Java tutorial
/******************************************************************************* * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.eclipse.xtext.formatting2.debug; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.AbstractRule; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.formatting2.regionaccess.IAstRegion; import org.eclipse.xtext.formatting2.regionaccess.IComment; import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion; import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion; import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegionPart; import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion; import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion; import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess; import org.eclipse.xtext.formatting2.regionaccess.ITextSegment; import org.eclipse.xtext.formatting2.regionaccess.IWhitespace; import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch; import org.eclipse.xtext.util.EmfFormatter; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; /** * @author Moritz Eysholdt - Initial contribution and API */ public class TextRegionAccessToString { protected static enum AstRegionComparator implements Comparator<IAstRegion> { CHILDREN_FIRST { @Override public int compare(IAstRegion o1, IAstRegion o2) { EObject e1 = o1.getSemanticElement(); EObject e2 = o2.getSemanticElement(); if (e1 == e2) return 0; if (EcoreUtil.isAncestor(e1, e2)) return 1; return -1; } }, CONTAINER_FIRST { @Override public int compare(IAstRegion o1, IAstRegion o2) { EObject e1 = o1.getSemanticElement(); EObject e2 = o2.getSemanticElement(); if (e1 == e2) return 0; if (EcoreUtil.isAncestor(e1, e2)) return -1; return 1; } }; } private static final int TITLE_WIDTH = 2; private static final String EMPTY_TITLE = Strings.repeat(" ", TITLE_WIDTH); private static final String EOBJECT_BEGIN_PADDED = Strings.padEnd("B", TITLE_WIDTH, ' '); private static final String EOBJECT_END_PADDED = Strings.padEnd("E", TITLE_WIDTH, ' '); private static final String HIDDEN = "H"; private static final String HIDDEN_PADDED = Strings.padEnd(HIDDEN, TITLE_WIDTH, ' '); private static final String SEMANTIC_PADDED = Strings.padEnd("S", TITLE_WIDTH, ' '); private Function<AbstractElement, String> grammarToString = new GrammarElementTitleSwitch().showRule() .showAssignments().showQualified(); private boolean hideColumnExplanation = false; private boolean hideIndentation = false; private boolean hightlightOrigin = false; private ITextSegment origin; private int textWidth = 20; public int getTextWidth() { return textWidth; } public TextRegionAccessToString hideColumnExplanation() { this.hideColumnExplanation = true; return this; } public TextRegionAccessToString hideIndentation() { this.hideIndentation = true; return this; } public TextRegionAccessToString hightlightOrigin() { this.hightlightOrigin = true; return this; } public boolean isHideColumnExplanation() { return hideColumnExplanation; } public boolean isHideIndentation() { return hideIndentation; } public boolean isHightlightOrigin() { return hightlightOrigin; } protected String quote(String string) { if (string == null) return "null"; string = string.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t"); int textLen = textWidth - 2; if (string.length() > textLen) return "\"" + string.substring(0, textLen - 3) + "...\""; else return Strings.padEnd("\"" + string + "\"", textWidth, ' '); } protected String toClassWithName(EObject obj) { String className = obj.eClass().getName(); EStructuralFeature nameFeature = obj.eClass().getEStructuralFeature("name"); if (nameFeature != null) { Object name = obj.eGet(nameFeature); if (name != null) return className + "'" + name + "'"; } return className; } @Override public String toString() { List<ITextSegment> list = toTokenAndGapList(); if (list.isEmpty()) return "(empty)"; Multimap<IHiddenRegion, IEObjectRegion> hiddens = LinkedListMultimap.create(); List<String> errors = Lists.newArrayList(); ITextRegionAccess access = list.get(0).getTextRegionAccess(); TreeIterator<EObject> all = EcoreUtil2.eAll(access.regionForRootEObject().getSemanticElement()); while (all.hasNext()) { EObject element = all.next(); IEObjectRegion obj = access.regionForEObject(element); if (obj == null) continue; IHiddenRegion previous = obj.getPreviousHiddenRegion(); IHiddenRegion next = obj.getNextHiddenRegion(); if (previous == null) errors.add("ERROR: " + EmfFormatter.objPath(element) + " has no leading HiddenRegion."); else hiddens.put(previous, obj); if (previous != next) { if (next == null) errors.add("ERROR: " + EmfFormatter.objPath(element) + " has no trailing HiddenRegion."); else hiddens.put(next, obj); } } TextRegionListToString result = new TextRegionListToString(); if (!hideColumnExplanation) { result.add("Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement", false); result.add("Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion", false); result.add("", false); } for (String error : errors) result.add(error, false); int indentation = 0, min = 0; for (ITextSegment region : list) { if (region instanceof IHiddenRegion) { Collection<IEObjectRegion> found = hiddens.get((IHiddenRegion) region); for (IEObjectRegion obj : found) { boolean p = obj.getNextHiddenRegion().equals(region); boolean n = obj.getPreviousHiddenRegion().equals(region); if (p) indentation--; else if (n) indentation++; if (indentation < min) min = indentation; } } } indentation = min < 0 ? min * -1 : 0; for (ITextSegment region : list) { List<IEObjectRegion> previous = Lists.newArrayList(); List<IEObjectRegion> next = Lists.newArrayList(); List<String> middle = Lists.newArrayList(toString(region)); if (region instanceof IHiddenRegion) { Collection<IEObjectRegion> found = hiddens.get((IHiddenRegion) region); for (IEObjectRegion obj : found) { boolean p = obj.getNextHiddenRegion().equals(region); boolean n = obj.getPreviousHiddenRegion().equals(region); if (p && n) middle.add(EMPTY_TITLE + "Semantic " + toString(obj)); else if (p) previous.add(obj); else if (n) next.add(obj); } Collections.sort(previous, AstRegionComparator.CHILDREN_FIRST); Collections.sort(next, AstRegionComparator.CONTAINER_FIRST); } for (IEObjectRegion obj : previous) { indentation--; result.add(indent(indentation) + EOBJECT_END_PADDED + toString(obj)); } String indent = indent(indentation); result.add(region, indent + Joiner.on("\n").join(middle).replace("\n", "\n" + indent)); for (IEObjectRegion obj : next) { result.add(indent(indentation) + EOBJECT_BEGIN_PADDED + toString(obj)); indentation++; } } return result.toString(); } protected String indent(int indentation) { if (hideIndentation) return ""; return Strings.repeat(" ", indentation); } protected String toString(AbstractRule rule) { return rule == null ? "null" : rule.getName(); } protected String toString(EObject ele) { if (ele instanceof AbstractElement) return grammarToString.apply((AbstractElement) ele); if (ele instanceof AbstractRule) return ele.eClass().getName() + "'" + ((AbstractRule) ele).getName() + "'"; return ele.toString(); } protected String toString(IComment comment) { String text = quote(comment.getText()); String gammar = toString(comment.getGrammarElement()); return String.format("%s Comment:%s", text, gammar); } protected String toString(IEObjectRegion region) { EObject obj = region.getSemanticElement(); StringBuilder builder = new StringBuilder(Strings.padEnd(toClassWithName(obj), textWidth, ' ') + " "); EObject element = region.getGrammarElement(); if (element instanceof AbstractElement) builder.append(grammarToString.apply((AbstractElement) element)); else if (element instanceof AbstractRule) builder.append(((AbstractRule) element).getName()); else builder.append(": ERROR: No grammar element."); List<String> segments = Lists.newArrayList(); EObject current = obj; while (current.eContainer() != null) { EObject container = current.eContainer(); EStructuralFeature containingFeature = current.eContainingFeature(); StringBuilder segment = new StringBuilder(); segment.append(toClassWithName(container)); segment.append("/"); segment.append(containingFeature.getName()); if (containingFeature.isMany()) { int index = ((List<?>) container.eGet(containingFeature)).indexOf(current); segment.append("[" + index + "]"); } current = container; segments.add(segment.toString()); } if (!segments.isEmpty()) { builder.append(" path:"); builder.append(Joiner.on("=").join(segments)); } return builder.toString(); } protected String toString(IHiddenRegion hiddens) { List<IHiddenRegionPart> parts = hiddens.getParts(); if (parts.isEmpty()) return HIDDEN; List<String> children = Lists.newArrayListWithExpectedSize(parts.size()); children.add(HIDDEN_PADDED + toString(parts.get(0))); for (int i = 1; i < parts.size(); i++) children.add(EMPTY_TITLE + toString(parts.get(i))); return Joiner.on("\n").join(children); } protected String toString(ISemanticRegion token) { String text = quote(token.getText()); return String.format("%s%s %s", SEMANTIC_PADDED, text, toString(token.getGrammarElement())); } protected String toString(ITextSegment region) { String result; if (region instanceof IEObjectRegion) result = toString((IEObjectRegion) region); else if (region instanceof ISemanticRegion) result = toString((ISemanticRegion) region); else if (region instanceof IHiddenRegion) result = toString((IHiddenRegion) region); else if (region instanceof IWhitespace) result = toString((IWhitespace) region); else if (region instanceof IComment) result = toString((IComment) region); else if (region != null) result = region.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(region)); else result = "null"; if (hightlightOrigin && region == origin) return ">>>" + result + "<<<"; return result; } protected String toString(IWhitespace whitespace) { String text = quote(whitespace.getText()); String grammar = toString(whitespace.getGrammarElement()); return String.format("%s Whitespace:%s", text, grammar); } protected List<ITextSegment> toTokenAndGapList() { final int range = this.hightlightOrigin ? 4 : (Integer.MAX_VALUE / 2); ITextSegment first = null; { ITextSegment current = origin; for (int i = 0; i < range && current != null; i++) { first = current; if (current instanceof ITextRegionAccess) current = ((ITextRegionAccess) current).regionForRootEObject().getPreviousHiddenRegion(); else if (current instanceof ISequentialRegion) current = ((ISequentialRegion) current).getPreviousHiddenRegion(); else if (current instanceof IHiddenRegionPart) current = ((IHiddenRegionPart) current).getHiddenRegion(); else throw new IllegalStateException("Unexpected Type: " + current.getClass()); } } if (first == null) return Collections.emptyList(); List<ITextSegment> result = Lists.newArrayList(); { ITextSegment current = first; for (int i = 0; i <= (range * 2) && current != null; i++) { result.add(current); if (current instanceof ISemanticRegion) current = ((ISemanticRegion) current).getNextHiddenRegion(); else if (current instanceof IHiddenRegion) current = ((IHiddenRegion) current).getNextSemanticRegion(); else throw new IllegalStateException("Unexpected Type: " + current.getClass()); } } return result; } public TextRegionAccessToString withOrigin(ITextSegment origin) { this.origin = origin; return this; } public TextRegionAccessToString withRegionAccess(ITextRegionAccess access) { this.origin = access.regionForRootEObject(); this.hightlightOrigin = false; return this; } public TextRegionAccessToString withTextWidth(int width) { this.textWidth = width; return this; } }