/*
* This file is part of GumTree.
*
* GumTree is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GumTree is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with GumTree. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2011-2015 Jean-Rémy Falleri <jr.falleri@gmail.com>
* Copyright 2011-2015 Floréal Morandat <florealm@gmail.com>
*/
package com.github.gumtreediff.tree;
import com.github.gumtreediff.io.TreeIoUtils;
import com.github.gumtreediff.io.TreeIoUtils.MetadataSerializer;
import com.github.gumtreediff.io.TreeIoUtils.MetadataUnserializer;
import com.github.gumtreediff.io.TreeIoUtils.TreeFormatter;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Pattern;
public class TreeContext {
Map<Integer, String> typeLabels = new HashMap<>();
final Map<String, Object> metadata = new HashMap<>();
final MetadataSerializers serializers = new MetadataSerializers();
ITree root;
@Override
public String toString() {
return TreeIoUtils.toLisp(this).toString();
}
public void setRoot(ITree root) {
this.root = root;
}
public ITree getRoot() {
return root;
}
public String getTypeLabel(ITree tree) {
return getTypeLabel(tree.getType());
}
public String getTypeLabel(int type) {
String tl = typeLabels.get(type);
if (tl == null)
tl = Integer.toString(type);
return tl;
}
protected void registerTypeLabel(int type, String name) {
if (name == null || name.equals(ITree.NO_LABEL))
return;
String typeLabel = typeLabels.get(type);
if (typeLabel == null) {
typeLabels.put(type, name);
} else if (!typeLabel.equals(name))
throw new RuntimeException(String.format("Redefining type %d: '%s' with '%s'", type, typeLabel, name));
}
public ITree createTree(int type, String label, String typeLabel) {
registerTypeLabel(type, typeLabel);
return new Tree(type, label);
}
public ITree createTree(ITree... trees) {
return new AbstractTree.FakeTree(trees);
}
public void validate() {
root.refresh();
TreeUtils.postOrderNumbering(root);
}
public boolean hasLabelFor(int type) {
return typeLabels.containsKey(type);
}
/**
* Get a global metadata.
* There is no way to know if the metadata is really null or does not exists.
*
* @param key of metadata
* @return the metadata or null if not found
*/
public Object getMetadata(String key) {
return metadata.get(key);
}
/**
* Get a local metadata, if available. Otherwise get a global metadata.
* There is no way to know if the metadata is really null or does not exists.
*
* @param key of metadata
* @return the metadata or null if not found
*/
public Object getMetadata(ITree node, String key) {
Object metadata;
if (node == null || (metadata = node.getMetadata(key)) == null)
return getMetadata(key);
return metadata;
}
/**
* Store a global metadata.
*
* @param key of the metadata
* @param value of the metadata
* @return the previous value of metadata if existed or null
*/
public Object setMetadata(String key, Object value) {
return metadata.put(key, value);
}
/**
* Store a local metadata
*
* @param key of the metadata
* @param value of the metadata
* @return the previous value of metadata if existed or null
*/
public Object setMetadata(ITree node, String key, Object value) {
if (node == null)
return setMetadata(key, value);
else {
Object res = node.setMetadata(key, value);
if (res == null)
return getMetadata(key);
return res;
}
}
/**
* Get an iterator on global metadata only
*/
public Iterator<Entry<String, Object>> getMetadata() {
return metadata.entrySet().iterator();
}
/**
* Get serializers for this tree context
*/
public MetadataSerializers getSerializers() {
return serializers;
}
public TreeContext export(MetadataSerializers s) {
serializers.addAll(s);
return this;
}
public TreeContext export(String key, MetadataSerializer s) {
serializers.add(key, s);
return this;
}
public TreeContext export(String... name) {
for (String n : name)
serializers.add(n, x -> x.toString());
return this;
}
/**
* Get an iterator on local and global metadata.
* To only get local metadata, simply use : `node.getMetadata()`
*/
public Iterator<Entry<String, Object>> getMetadata(ITree node) {
if (node == null)
return getMetadata();
return new Iterator<Entry<String, Object>>() {
final Iterator<Entry<String, Object>> localIterator = node.getMetadata();
final Iterator<Entry<String, Object>> globalIterator = getMetadata();
final Set<String> seenKeys = new HashSet<>();
Iterator<Entry<String, Object>> currentIterator = localIterator;
Entry<String, Object> nextEntry;
{
next();
}
@Override
public boolean hasNext() {
return nextEntry != null;
}
@Override
public Entry<String, Object> next() {
Entry<String, Object> n = nextEntry;
if (currentIterator == localIterator) {
if (localIterator.hasNext()) {
nextEntry = localIterator.next();
seenKeys.add(nextEntry.getKey());
return n;
} else {
currentIterator = globalIterator;
}
}
nextEntry = null;
while (globalIterator.hasNext()) {
Entry<String, Object> e = globalIterator.next();
if (!(seenKeys.contains(e.getKey()) || (e.getValue() == null))) {
nextEntry = e;
seenKeys.add(nextEntry.getKey());
break;
}
}
return n;
}
};
}
public static class Marshallers<E> {
Map<String, E> serializers = new HashMap<>();
public static final Pattern valid_id = Pattern.compile("[a-zA-Z0-9_]*");
public void addAll(Marshallers<E> other) {
addAll(other.serializers);
}
public void addAll(Map<String, E> serializers) {
serializers.forEach((k, s) -> add(k, s));
}
public void add(String name, E serializer) {
if (!valid_id.matcher(name).matches()) // TODO I definitely don't like this rule, we should think twice
throw new RuntimeException("Invalid key for serialization");
serializers.put(name, serializer);
}
public void remove(String key) {
serializers.remove(key);
}
public Set<String> exports() {
return serializers.keySet();
}
}
public static class MetadataSerializers extends Marshallers<MetadataSerializer> {
public void serialize(TreeFormatter formatter, String key, Object value) throws Exception {
MetadataSerializer s = serializers.get(key);
if (s != null)
formatter.serializeAttribute(key, s.toString(value));
}
}
public static class MetadataUnserializers extends Marshallers<MetadataUnserializer> {
public void load(ITree tree, String key, String value) throws Exception {
MetadataUnserializer s = serializers.get(key);
if (s != null)
tree.setMetadata(key, s.fromString(value));
}
}
}