package com.ctrip.framework.apollo.portal.component.txtresolver;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* normal property file resolver.
* update comment and blank item implement by create new item and delete old item.
* update normal key/value item implement by update.
*/
@Component("propertyResolver")
public class PropertyResolver implements ConfigTextResolver {
private static final String KV_SEPARATOR = "=";
private static final String ITEM_SEPARATOR = "\n";
@Override
public ItemChangeSets resolve(long namespaceId, String configText, List<ItemDTO> baseItems) {
Map<Integer, ItemDTO> oldLineNumMapItem = BeanUtils.mapByKey("lineNum", baseItems);
Map<String, ItemDTO> oldKeyMapItem = BeanUtils.mapByKey("key", baseItems);
//remove comment and blank item map.
oldKeyMapItem.remove("");
String[] newItems = configText.split(ITEM_SEPARATOR);
if (isHasRepeatKey(newItems)) {
throw new BadRequestException("config text has repeat key please check.");
}
ItemChangeSets changeSets = new ItemChangeSets();
Map<Integer, String> newLineNumMapItem = new HashMap<Integer, String>();//use for delete blank and comment item
int lineCounter = 1;
for (String newItem : newItems) {
newItem = newItem.trim();
newLineNumMapItem.put(lineCounter, newItem);
ItemDTO oldItemByLine = oldLineNumMapItem.get(lineCounter);
//comment item
if (isCommentItem(newItem)) {
handleCommentLine(namespaceId, oldItemByLine, newItem, lineCounter, changeSets);
//blank item
} else if (isBlankItem(newItem)) {
handleBlankLine(namespaceId, oldItemByLine, lineCounter, changeSets);
//normal item
} else {
handleNormalLine(namespaceId, oldKeyMapItem, newItem, lineCounter, changeSets);
}
lineCounter++;
}
deleteCommentAndBlankItem(oldLineNumMapItem, newLineNumMapItem, changeSets);
deleteNormalKVItem(oldKeyMapItem, changeSets);
return changeSets;
}
private boolean isHasRepeatKey(String[] newItems) {
Set<String> keys = new HashSet<>();
int lineCounter = 1;
int keyCount = 0;
for (String item : newItems) {
if (!isCommentItem(item) && !isBlankItem(item)) {
keyCount++;
String[] kv = parseKeyValueFromItem(item);
if (kv != null) {
keys.add(kv[0]);
} else {
throw new BadRequestException("line:" + lineCounter + " key value must separate by '='");
}
}
lineCounter++;
}
return keyCount > keys.size();
}
private String[] parseKeyValueFromItem(String item) {
int kvSeparator = item.indexOf(KV_SEPARATOR);
if (kvSeparator == -1) {
return null;
}
String[] kv = new String[2];
kv[0] = item.substring(0, kvSeparator).trim();
kv[1] = item.substring(kvSeparator + 1, item.length()).trim();
return kv;
}
private void handleCommentLine(Long namespaceId, ItemDTO oldItemByLine, String newItem, int lineCounter, ItemChangeSets changeSets) {
String oldComment = oldItemByLine == null ? "" : oldItemByLine.getComment();
//create comment. implement update comment by delete old comment and create new comment
if (!(isCommentItem(oldItemByLine) && newItem.equals(oldComment))) {
changeSets.addCreateItem(buildCommentItem(0l, namespaceId, newItem, lineCounter));
}
}
private void handleBlankLine(Long namespaceId, ItemDTO oldItem, int lineCounter, ItemChangeSets changeSets) {
if (!isBlankItem(oldItem)) {
changeSets.addCreateItem(buildBlankItem(0l, namespaceId, lineCounter));
}
}
private void handleNormalLine(Long namespaceId, Map<String, ItemDTO> keyMapOldItem, String newItem,
int lineCounter, ItemChangeSets changeSets) {
String[] kv = parseKeyValueFromItem(newItem);
if (kv == null) {
throw new BadRequestException("line:" + lineCounter + " key value must separate by '='");
}
String newKey = kv[0];
String newValue = kv[1].replace("\\n", "\n"); //handle user input \n
ItemDTO oldItem = keyMapOldItem.get(newKey);
if (oldItem == null) {//new item
changeSets.addCreateItem(buildNormalItem(0l, namespaceId, newKey, newValue, "", lineCounter));
} else if (!newValue.equals(oldItem.getValue()) || lineCounter != oldItem.getLineNum()) {//update item
changeSets.addUpdateItem(
buildNormalItem(oldItem.getId(), namespaceId, newKey, newValue, oldItem.getComment(),
lineCounter));
}
keyMapOldItem.remove(newKey);
}
private boolean isCommentItem(ItemDTO item) {
return item != null && "".equals(item.getKey())
&& (item.getComment().startsWith("#") || item.getComment().startsWith("!"));
}
private boolean isCommentItem(String line) {
return line != null && (line.startsWith("#") || line.startsWith("!"));
}
private boolean isBlankItem(ItemDTO item) {
return item != null && "".equals(item.getKey()) && "".equals(item.getComment());
}
private boolean isBlankItem(String line) {
return "".equals(line);
}
private void deleteNormalKVItem(Map<String, ItemDTO> baseKeyMapItem, ItemChangeSets changeSets) {
//surplus item is to be deleted
for (Map.Entry<String, ItemDTO> entry : baseKeyMapItem.entrySet()) {
changeSets.addDeleteItem(entry.getValue());
}
}
private void deleteCommentAndBlankItem(Map<Integer, ItemDTO> oldLineNumMapItem,
Map<Integer, String> newLineNumMapItem,
ItemChangeSets changeSets) {
for (Map.Entry<Integer, ItemDTO> entry : oldLineNumMapItem.entrySet()) {
int lineNum = entry.getKey();
ItemDTO oldItem = entry.getValue();
String newItem = newLineNumMapItem.get(lineNum);
//1. old is blank by now is not
//2.old is comment by now is not exist or modified
if ((isBlankItem(oldItem) && !isBlankItem(newItem))
|| isCommentItem(oldItem) && (newItem == null || !newItem.equals(oldItem.getComment()))) {
changeSets.addDeleteItem(oldItem);
}
}
}
private ItemDTO buildCommentItem(Long id, Long namespaceId, String comment, int lineNum) {
return buildNormalItem(id, namespaceId, "", "", comment, lineNum);
}
private ItemDTO buildBlankItem(Long id, Long namespaceId, int lineNum) {
return buildNormalItem(id, namespaceId, "", "", "", lineNum);
}
private ItemDTO buildNormalItem(Long id, Long namespaceId, String key, String value, String comment, int lineNum) {
ItemDTO item = new ItemDTO(key, value, comment, lineNum);
item.setId(id);
item.setNamespaceId(namespaceId);
return item;
}
}