From ee410e4835941dac531a3dd7a859d3942390b922 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?=
Date: Thu, 26 Jul 2018 10:58:42 +0200
Subject: [PATCH 01/16] feat: detailed tree of SourceFragments of
CompilationUnit
---
.../spoon/reflect/cu/CompilationUnit.java | 16 +
.../change/CollectionSourceFragment.java | 86 ++
.../printer/change/ElementSourceFragment.java | 874 ++++++++++++++++++
.../printer/change/SourceFragment.java | 26 +
.../printer/change/TokenSourceFragment.java | 49 +
.../visitor/printer/change/TokenType.java | 30 +
.../reflect/cu/CompilationUnitImpl.java | 32 +-
src/test/java/spoon/MavenLauncherTest.java | 6 +-
.../SpoonArchitectureEnforcerTest.java | 1 +
src/test/java/spoon/test/main/MainTest.java | 38 +-
.../test/position/TestSourceFragment.java | 223 +++++
.../testclasses/FooSourceFragments.java | 32 +
.../MethodWithJavaDocAndModifiers.java | 12 +
13 files changed, 1420 insertions(+), 5 deletions(-)
create mode 100644 src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java
create mode 100644 src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
create mode 100644 src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java
create mode 100644 src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java
create mode 100644 src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
create mode 100644 src/test/java/spoon/test/position/TestSourceFragment.java
create mode 100644 src/test/java/spoon/test/position/testclasses/FooSourceFragments.java
create mode 100644 src/test/java/spoon/test/position/testclasses/MethodWithJavaDocAndModifiers.java
diff --git a/src/main/java/spoon/reflect/cu/CompilationUnit.java b/src/main/java/spoon/reflect/cu/CompilationUnit.java
index cfd1d14ba35..fed60343a1e 100644
--- a/src/main/java/spoon/reflect/cu/CompilationUnit.java
+++ b/src/main/java/spoon/reflect/cu/CompilationUnit.java
@@ -20,6 +20,8 @@
import spoon.reflect.declaration.CtModule;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtType;
+import spoon.reflect.visitor.printer.change.ElementSourceFragment;
+import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtImport;
import spoon.support.Experimental;
@@ -165,4 +167,18 @@ enum UNIT_TYPE {
@Experimental
void setImports(Set imports);
+ /**
+ * @return root {@link ElementSourceFragment} of the tree of all the source fragments of origin source code of this compilation unit
+ * Note: this method creates tree of {@link ElementSourceFragment}s ... it can be a lot of instances
+ * If the tree of {@link ElementSourceFragment}s is needed then it MUST be created
+ * BEFORE the Spoon model of this {@link CompilationUnit} is changed. The best creation time is immediately after Spoon model is built.
+ * Otherwise there might be missing some {@link ElementSourceFragment}s when {@link CtElement}s are deleted
+ */
+ ElementSourceFragment getRootSourceFragment();
+
+ /**
+ * @param element the {@link CtElement} whose origin source code is needed.
+ * @return {@link ElementSourceFragment} which mirrors the origin source code of the `element` or null.
+ */
+ ElementSourceFragment getSourceFragment(SourcePositionHolder element);
}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java
new file mode 100644
index 00000000000..4aab9201213
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.reflect.visitor.printer.change;
+
+import java.util.List;
+
+import spoon.reflect.meta.ContainerKind;
+import spoon.reflect.path.CtRole;
+
+/**
+ * {@link SourceFragment} of List or Set of {@link ElementSourceFragment}s which belongs to collection role.
+ * For example list of Type members or list of parameters, etc.
+ * Or set of modifiers and annotations
+ */
+public class CollectionSourceFragment implements SourceFragment {
+
+ private final List items;
+
+ public CollectionSourceFragment(List items) {
+ super();
+ this.items = items;
+ }
+
+ @Override
+ public String getSourceCode() {
+ StringBuilder sb = new StringBuilder();
+ for (SourceFragment childSourceFragment : items) {
+ sb.append(childSourceFragment.getSourceCode());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @return child source fragments of this collection
+ */
+ public List getItems() {
+ return items;
+ }
+
+ @Override
+ public String toString() {
+ return items.toString();
+ }
+
+ /**
+ * @return true if collection contains only children of one role handler with container kind LIST
+ */
+ public boolean isOrdered() {
+ CtRole role = null;
+ ContainerKind kind = null;
+ for (SourceFragment childSourceFragment : items) {
+ if (childSourceFragment instanceof ElementSourceFragment) {
+ ElementSourceFragment esf = (ElementSourceFragment) childSourceFragment;
+ if (role == null) {
+ role = esf.getRoleInParent();
+ kind = esf.getContainerKindInParent();
+ if (kind != ContainerKind.LIST) {
+ return false;
+ }
+ } else {
+ if (role != esf.getRoleInParent()) {
+ //the collection contains elements of different roles. It cannot be ordered
+ return false;
+ }
+ //else there is another element of the same role - ok
+ }
+ }
+ }
+ //there are only elements of one role of container kind LIST
+ return true;
+ }
+}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
new file mode 100644
index 00000000000..50bcf993d91
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
@@ -0,0 +1,874 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.reflect.visitor.printer.change;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import spoon.SpoonException;
+import spoon.reflect.code.CtComment;
+import spoon.reflect.code.CtLiteral;
+import spoon.reflect.cu.CompilationUnit;
+import spoon.reflect.cu.SourcePosition;
+import spoon.reflect.cu.SourcePositionHolder;
+import spoon.reflect.declaration.CtElement;
+import spoon.reflect.declaration.CtModifiable;
+import spoon.reflect.meta.ContainerKind;
+import spoon.reflect.meta.RoleHandler;
+import spoon.reflect.meta.impl.RoleHandlerHelper;
+import spoon.reflect.path.CtRole;
+import spoon.reflect.visitor.CtScanner;
+import spoon.support.reflect.CtExtendedModifier;
+import spoon.support.reflect.cu.position.SourcePositionImpl;
+
+/**
+ * Represents a part of source code of an {@link CtElement}
+ * It is connected into a tree of {@link ElementSourceFragment}s.
+ * That tree can be build by {@link CompilationUnit#getRootSourceFragment()}
+ * And the tree of {@link ElementSourceFragment}s related to one element can be returned by {@link CompilationUnit#getSourceFragment(SourcePositionHolder)}
+ */
+public class ElementSourceFragment implements SourceFragment {
+
+ private final SourcePositionHolder element;
+ private final RoleHandler roleHandlerInParent;
+ private ElementSourceFragment nextSibling;
+ private ElementSourceFragment firstChild;
+
+ /**
+ * Creates a source fragment of {@link SourcePositionHolder}
+ *
+ * @param element target {@link SourcePositionHolder}
+ * @param roleHandlerInParent The {@link RoleHandler}, which defines role of target `element` in it's parent
+ */
+ public ElementSourceFragment(SourcePositionHolder element, RoleHandler roleHandlerInParent) {
+ this.element = element;
+ this.roleHandlerInParent = roleHandlerInParent;
+ }
+
+ /**
+ * @return offset of first character which belongs to this fragment
+ */
+ public int getStart() {
+ if (firstChild != null) {
+ return Math.min(getSourcePosition().getSourceStart(), firstChild.getStart());
+ }
+ return getSourcePosition().getSourceStart();
+ }
+
+ /**
+ * @return offset of character after this fragment
+ */
+ public int getEnd() {
+ if (firstChild != null) {
+ return Math.max(getSourcePosition().getSourceEnd() + 1, firstChild.getLastSibling().getEnd());
+ }
+ return getSourcePosition().getSourceEnd() + 1;
+ }
+
+ /**
+ * @return {@link SourcePosition} of this fragment
+ */
+ public SourcePosition getSourcePosition() {
+ return element.getPosition();
+ }
+
+ @Override
+ public String toString() {
+ return "|" + getStart() + ", " + getEnd() + "|" + getSourceCode() + "|";
+ }
+
+ /**
+ * @return origin source code of this fragment
+ */
+ @Override
+ public String getSourceCode() {
+ return getSourceCode(getStart(), getEnd());
+ }
+
+ /**
+ * @param start start offset relative to compilation unit
+ * @param end end offset (after last character) relative to compilation unit
+ * @return source code of this Fragment between start/end offsets
+ */
+ public String getSourceCode(int start, int end) {
+ String src = getOriginalSourceCode();
+ if (src != null) {
+ return src.substring(start, end);
+ }
+ return null;
+ }
+
+ /**
+ * @return true if position points to same compilation unit (source file) as this SourceFragment
+ */
+ private boolean isFromSameSource(SourcePosition position) {
+ return getSourcePosition().getCompilationUnit().equals(position.getCompilationUnit());
+ }
+
+ /**
+ * Builds tree of {@link SourcePosition}s of `element` and all it's children
+ * @param element the root element of the tree
+ */
+ public void addTreeOfSourceFragmentsOfElement(CtElement element) {
+ SourcePosition sp = element.getPosition();
+ Deque parents = new ArrayDeque<>();
+ parents.push(this);
+ //scan all children of `element` and build tree of SourceFragments
+ new CtScanner() {
+ CtRole scannedRole;
+ public void scan(CtRole role, CtElement element) {
+ scannedRole = role;
+ super.scan(role, element);
+ }
+ @Override
+ protected void enter(CtElement e) {
+ ElementSourceFragment newFragment = addChild(parents.peek(), scannedRole, e);
+ if (newFragment != null) {
+ parents.push(newFragment);
+ if (e instanceof CtModifiable) {
+ CtModifiable modifiable = (CtModifiable) e;
+ Set modifiers = modifiable.getExtendedModifiers();
+ for (CtExtendedModifier ctExtendedModifier : modifiers) {
+ addChild(newFragment, CtRole.MODIFIER, ctExtendedModifier);
+ }
+ }
+ }
+ }
+ @Override
+ protected void exit(CtElement e) {
+ ElementSourceFragment topFragment = parents.peek();
+ if (topFragment != null && topFragment.getElement() == e) {
+ parents.pop();
+ }
+ }
+ }.scan(element.getRoleInParent(), element);
+ }
+ /**
+ * @param parentFragment the parent {@link ElementSourceFragment}, which will receive {@link ElementSourceFragment} made for `otherElement`
+ * @param roleInParent the {@link CtRole} of `otherElement` in scope of element of `parentFragment`
+ * @param otherElement {@link SourcePositionHolder} whose {@link ElementSourceFragment} has to be added to `parentFragment`
+ * @return new {@link ElementSourceFragment} created for `otherElement` or null if `otherElement` has no source position or doesn't belong to the same compilation unit
+ */
+ private ElementSourceFragment addChild(ElementSourceFragment parentFragment, CtRole roleInParent, SourcePositionHolder otherElement) {
+ SourcePosition otherSourcePosition = otherElement.getPosition();
+ if (otherSourcePosition instanceof SourcePositionImpl && otherSourcePosition.getCompilationUnit() != null) {
+ SourcePositionImpl childSPI = (SourcePositionImpl) otherSourcePosition;
+ if (parentFragment.isFromSameSource(otherSourcePosition)) {
+ ElementSourceFragment otherFragment = new ElementSourceFragment(otherElement, parentFragment.getRoleHandler(roleInParent, otherElement));
+ //parent and child are from the same file. So we can connect their positions into one tree
+ CMP cmp = parentFragment.compare(otherFragment);
+ if (cmp == CMP.OTHER_IS_CHILD) {
+ //child belongs under parent - OK
+ parentFragment.addChild(otherFragment);
+ return otherFragment;
+ } else {
+ if (cmp == CMP.OTHER_IS_AFTER || cmp == CMP.OTHER_IS_BEFORE) {
+ if (otherElement instanceof CtComment) {
+ /*
+ * comments of elements are sometime not included in source position of element.
+ * because comments are ignored tokens for java compiler, which computes start/end of elements
+ * Example:
+ *
+ * //a comment
+ * aStatement();
+ *
+ */
+ if (otherFragment.getStart() == 0) {
+ //it is CompilationUnit comment, which is before package and imports, so it doesn't belong to class
+ //No problem. Simply add comment at correct position into SourceFragment tree, starting from ROOT
+ addChild(otherFragment);
+ return otherFragment;
+ }
+ //add this child into parent's source fragment and extend that parent source fragment
+ parentFragment.addChild(otherFragment);
+ return otherFragment;
+ }
+ }
+ //the source position of child element is not included in source position of parent element
+ //I (Pavel) am not sure how to handle it, so let's wait until it happens...
+// if (otherElement instanceof CtAnnotation>) {
+// /*
+// * it can happen for annotations of type TYPE_USE and FIELD
+// * In such case the annotation belongs to 2 elements
+// * And one of them cannot have matching source position - OK
+// */
+// return null;
+// }
+ //something is wrong ...
+ throw new SpoonException("The SourcePosition of elements are not consistent\nparentFragment: " + parentFragment + "\notherFragment: " + otherFragment);
+ }
+ } else {
+ throw new SpoonException("SourcePosition from unexpected compilation unit: " + otherSourcePosition + " expected is: " + parentFragment.getSourcePosition());
+ }
+ }
+ //do not connect that undefined source position
+ return null;
+ }
+
+ private RoleHandler getRoleHandler(CtRole roleInParent, SourcePositionHolder otherElement) {
+ SourcePositionHolder parent = element;
+ if (parent instanceof CompilationUnit) {
+ parent = null;
+ }
+ if (parent == null) {
+ if (otherElement instanceof CtElement) {
+ parent = ((CtElement) otherElement).getParent();
+ }
+ }
+ if (parent instanceof CtElement) {
+ CtElement ele = (CtElement) parent;
+ return RoleHandlerHelper.getRoleHandler(ele.getClass(), roleInParent);
+ }
+ return null;
+ }
+
+ /**
+ * adds `other` {@link ElementSourceFragment} into tree of {@link ElementSourceFragment}s represented by this root element
+ *
+ * @param other to be added {@link ElementSourceFragment}
+ * @return new root of the tree of the {@link ElementSourceFragment}s. It can be be this or `other`
+ */
+ public ElementSourceFragment add(ElementSourceFragment other) {
+ if (this == other) {
+ throw new SpoonException("SourceFragment#add must not be called twice for the same SourceFragment");
+ //optionally we might accept that and simply return this
+ }
+ CMP cmp = this.compare(other);
+ switch (cmp) {
+ case OTHER_IS_AFTER:
+ //other is after this
+ addNextSibling(other);
+ return this;
+ case OTHER_IS_BEFORE:
+ //other is before this
+ other.addNextSibling(this);
+ return other;
+ case OTHER_IS_CHILD:
+ //other is child of this
+ addChild(other);
+ return this;
+ case OTHER_IS_PARENT:
+ //other is parent of this, merge this and all siblings of `this` as children and siblings of `other`
+ other.merge(this);
+ return other;
+ }
+ throw new SpoonException("Unexpected compare result: " + cmp);
+ }
+
+ private void merge(ElementSourceFragment tobeMerged) {
+ while (tobeMerged != null) {
+ ElementSourceFragment nextTobeMerged = tobeMerged.getNextSibling();
+ //disconnect tobeMerged from nextSiblings before we add it. So it is added individually and not with wrong siblings too
+ tobeMerged.nextSibling = null;
+ add(tobeMerged);
+ tobeMerged = nextTobeMerged;
+ }
+ }
+
+ /**
+ * adds `fragment` as child fragment of this fragment. If child is located before or after this fragment,
+ * then start/end of this fragment is moved
+ * @param fragment to be add
+ */
+ public void addChild(ElementSourceFragment fragment) {
+ if (firstChild == null) {
+ firstChild = fragment;
+ } else {
+ firstChild = firstChild.add(fragment);
+ }
+ }
+
+ private void addNextSibling(ElementSourceFragment sibling) {
+ if (nextSibling == null) {
+ nextSibling = sibling;
+ } else {
+ nextSibling = nextSibling.add(sibling);
+ }
+ }
+
+ private ElementSourceFragment getLastSibling() {
+ ElementSourceFragment lastSibling = this;
+ while (lastSibling.nextSibling != null) {
+ lastSibling = lastSibling.nextSibling;
+ }
+ return lastSibling;
+ }
+
+ private enum CMP {
+ OTHER_IS_BEFORE,
+ OTHER_IS_AFTER,
+ OTHER_IS_CHILD,
+ OTHER_IS_PARENT
+ }
+
+ /**
+ * compares this and other
+ * @param other other {@link SourcePosition}
+ * @return CMP
+ * throws {@link SpoonException} if intervals overlap or start/end is negative
+ */
+ private CMP compare(ElementSourceFragment other) {
+ if (other == this) {
+ throw new SpoonException("SourcePositionImpl#addNextSibling must not be called twice for the same SourcePosition");
+ }
+ if (getEnd() <= other.getStart()) {
+ //other is after this
+ return CMP.OTHER_IS_AFTER;
+ }
+ if (other.getEnd() <= getStart()) {
+ //other is before this
+ return CMP.OTHER_IS_BEFORE;
+ }
+ if (getStart() <= other.getStart() && getEnd() >= other.getEnd()) {
+ //other is child of this
+ return CMP.OTHER_IS_CHILD;
+ }
+ if (getStart() >= other.getStart() && getEnd() <= other.getEnd()) {
+ //other is parent of this
+ return CMP.OTHER_IS_PARENT;
+ }
+ //the fragments overlap - it is not allowed
+ throw new SpoonException("Cannot compare this: [" + getStart() + ", " + getEnd() + "] with other: [\"" + other.getStart() + "\", \"" + other.getEnd() + "\"]");
+ }
+
+ /**
+ * @return {@link ElementSourceFragment} which belongs to the same parent and is next in the sources
+ */
+ public ElementSourceFragment getNextSibling() {
+ return nextSibling;
+ }
+
+ /**
+ * @return {@link ElementSourceFragment}, which is first child of this fragment
+ */
+ public ElementSourceFragment getFirstChild() {
+ return firstChild;
+ }
+
+ /**
+ * Searches the tree of fragments for the {@link ElementSourceFragment} with expected `element`,
+ * which contains `start` and `end` source interval.
+ * It searches in siblings and children of this {@link ElementSourceFragment} recursively.
+ * @param element the {@link SourcePositionHolder} of fragment it is looking for or null for any element
+ * @param start the start offset of searched fragment
+ * @param end the offset of next character after the end of searched fragment
+ *
+ * @return {@link ElementSourceFragment} which represents the root of the CtElement whose sources are in interval [start, end]
+ */
+ public ElementSourceFragment getSourceFragmentOf(SourcePositionHolder element, int start, int end) {
+ int myEnd = getEnd();
+ if (myEnd <= start) {
+ //search in next sibling
+ if (nextSibling == null) {
+ return null;
+ }
+ return getRootFragmentOfElement(nextSibling.getSourceFragmentOf(element, start, end));
+ }
+ int myStart = getStart();
+ if (myStart <= start) {
+ if (myEnd >= end) {
+ if (myStart == start && myEnd == end) {
+ //we have found exact match
+ if (element != null && getElement() != element) {
+ if (firstChild == null) {
+ throw new SpoonException("There is no source fragment for element " + element.getClass() + ". There is one for class " + getElement().getClass());
+ }
+ return firstChild.getSourceFragmentOf(element, start, end);
+ }
+ return this;
+ }
+ //it is the child
+ if (firstChild == null) {
+ if (element != null && getElement() != element) {
+ throw new SpoonException("There is no source fragment for element " + element.getClass() + ". There is one for class " + getElement().getClass());
+ }
+ return this;
+ }
+ ElementSourceFragment child = getRootFragmentOfElement(firstChild.getSourceFragmentOf(element, start, end));
+ if (child != null) {
+ //all children are smaller then this element
+ return child;
+ }
+ //so this fragment is last one which wraps whole element
+ if (element != null && getElement() != element) {
+ throw new SpoonException("There is no source fragment for element " + element.getClass() + ". There is one for class " + getElement().getClass());
+ }
+ return this;
+ }
+ //start - end overlaps over multiple fragments
+ throw new SpoonException("Invalid start/end interval. It overlaps multiple fragments.");
+ }
+ return null;
+ }
+
+ private ElementSourceFragment getRootFragmentOfElement(ElementSourceFragment childFragment) {
+ if (childFragment != null && getElement() != null && childFragment.getElement() == getElement()) {
+ //child fragment and this fragment have same element. Return this fragment,
+ //because we have to return root fragment of CtElement
+ return this;
+ }
+ return childFragment;
+ }
+ /**
+ * @return {@link CtElement} whose source code is contained in this fragment.
+ * May be null
+ */
+ public SourcePositionHolder getElement() {
+ return element;
+ }
+
+ /**
+ * Note: the List of children is flat. The child fragments of collections (parameters, type members, ...) are next to each other.
+ * @return list of child fragments of this {@link ElementSourceFragment}.
+ */
+ public List getChildrenFragments() {
+ if (element instanceof CtLiteral) {
+ return Collections.singletonList(new TokenSourceFragment(getSourceCode(), TokenType.LITERAL));
+ }
+ List children = new ArrayList<>();
+ int off = getStart();
+ ElementSourceFragment child = getFirstChild();
+ while (child != null) {
+ forEachConstantFragment(off, child.getStart(), cf -> children.add(cf));
+ children.add(child);
+ off = child.getEnd();
+ child = child.getNextSibling();
+ }
+ forEachConstantFragment(off, getEnd(), cf -> children.add(cf));
+ return children;
+ }
+
+ /**
+ * Detects all child fragments of this {@link ElementSourceFragment}.
+ * Note: the List of children contains one {@link CollectionSourceFragment} for each collection of fragments (parameters, type members, ...).
+ * Note: the {@link CollectionSourceFragment} may contain a mix of fragments of different roles, when they overlap.
+ * For example this code contains mix of annotations and modifiers
+ * public @Deprecated static @Ignored void method()
+ * @return list of child fragments of this {@link ElementSourceFragment} where fragments,
+ * which belongs to the same collection are grouped into {@link CollectionSourceFragment}
+ */
+ public List getGroupedChildrenFragments() {
+ List flatChildren = getChildrenFragments();
+ List result = new ArrayList<>();
+ int i = 0;
+ while (i < flatChildren.size()) {
+ SourceFragment child = flatChildren.get(i);
+ if (child instanceof TokenSourceFragment) {
+ result.add(child);
+ i++;
+ continue;
+ } else if (child instanceof ElementSourceFragment) {
+ ElementSourceFragment esf = (ElementSourceFragment) child;
+ ContainerKind kind = esf.getContainerKindInParent();
+ if (kind == ContainerKind.SINGLE) {
+ //it is root element or there is always only one child instance in parent
+ result.add(child);
+ i++;
+ continue;
+ }
+ //there can be 0, 1 or more items of children of the same role
+ //search for another element of the same role
+ Set foundRoles = new HashSet<>();
+ foundRoles.add(checkNotNull(esf.getRoleInParent()));
+ List childrenInSameCollection = new ArrayList<>();
+ //but first include prefix whitespace
+ SourceFragment spaceChild = removeSuffixSpace(result);
+ if (spaceChild != null) {
+ childrenInSameCollection.add(spaceChild);
+ }
+ childrenInSameCollection.add(esf);
+ int lastOfSameRole = findIndexOfLastChildTokenOfRoleHandler(flatChildren, i, esf.getRoleInParent());
+ //search for other roles in that interval
+ i++;
+ while (i <= lastOfSameRole) {
+ child = flatChildren.get(i);
+ childrenInSameCollection.add(child);
+ CtRole role = null;
+ if (child instanceof ElementSourceFragment) {
+ ElementSourceFragment esf2 = (ElementSourceFragment) child;
+ role = esf2.getRoleInParent();
+ }
+ if (role != null && role != CtRole.COMMENT && foundRoles.add(role)) {
+ //there is another role in same block, search for last one
+ lastOfSameRole = Math.max(lastOfSameRole, findIndexOfLastChildTokenOfRoleHandler(flatChildren, i + 1, role));
+ }
+ i++;
+ }
+ //add suffix space
+ if (i < flatChildren.size()) {
+ SourceFragment nextChild = flatChildren.get(i);
+ if (isSpaceFragment(nextChild)) {
+ childrenInSameCollection.add(nextChild);
+ i++;
+ }
+ }
+ result.add(new CollectionSourceFragment(childrenInSameCollection));
+ } else {
+ throw new SpoonException("Unexpected SourceFragment of type " + child.getClass());
+ }
+ }
+ return result;
+ }
+
+ private SourceFragment removeSuffixSpace(List list) {
+ if (list.size() > 0) {
+ SourceFragment lastChild = list.get(list.size() - 1);
+ if (isSpaceFragment(lastChild)) {
+ list.remove(list.size() - 1);
+ return lastChild;
+ }
+ }
+ return null;
+ }
+
+ private T checkNotNull(T o) {
+ if (o == null) {
+ throw new SpoonException("Unexpected null value");
+ }
+ return o;
+ }
+
+ private static int findIndexOfLastChildTokenOfRoleHandler(List childFragments, int start, CtRole role) {
+ return findIndexOfPreviousFragment(childFragments, start,
+ filter(ElementSourceFragment.class, fragment -> fragment.getRoleInParent() == role));
+ }
+
+ private enum CharType {
+ SPACE,
+ NON_SPACE;
+
+ static CharType fromChar(char c) {
+ return Character.isWhitespace(c) ? SPACE : NON_SPACE;
+ }
+ }
+
+ private static final Set separators = new HashSet<>(Arrays.asList("->", "::", "..."));
+ static {
+ "(){}[];,.:@=<>?&|".chars().forEach(c -> separators.add(new String(Character.toChars(c))));
+ }
+ private static final Set operators = new HashSet<>(Arrays.asList(
+ "=",
+ ">",
+ "<",
+ "!",
+ "~",
+ "?",
+ ":",
+ "==",
+ "<=",
+ ">=",
+ "!=",
+ "&&",
+ "||",
+ "++",
+ "--",
+ "+",
+ "-",
+ "*",
+ "/",
+ "&",
+ "|",
+ "^",
+ "%",
+ "<<", ">>", ">>>",
+
+ "+=",
+ "-=",
+ "*=",
+ "/=",
+ "&=",
+ "|=",
+ "^=",
+ "%=",
+ "<<=",
+ ">>=",
+ ">>>="/*,
+ it is handled as keyword here
+ "instanceof"
+ */
+ ));
+
+ private static final String[] javaKeywordsJoined = new String[] {
+ "abstract continue for new switch",
+ "assert default goto package synchronized",
+ "boolean do if private this",
+ "break double implements protected throw",
+ "byte else import public throws",
+ "case enum instanceof return transient",
+ "catch extends int short try",
+ "char final interface static void",
+ "class finally long strictfp volatile",
+ "const float native super while"};
+
+ private static final Set javaKeywords = new HashSet<>();
+ static {
+ for (String str : javaKeywordsJoined) {
+ StringTokenizer st = new StringTokenizer(str, " ");
+ while (st.hasMoreTokens()) {
+ javaKeywords.add(st.nextToken());
+ }
+ }
+ }
+
+ private static final List matchers = new ArrayList<>();
+ static {
+ separators.forEach(s -> matchers.add(new StringMatcher(s, TokenType.SEPARATOR)));
+ operators.forEach(s -> matchers.add(new StringMatcher(s, TokenType.OPERATOR)));
+ }
+
+ /**
+ * Calls `consumer` once for each constant {@link SourceFragment} found in source code between `start` and `end`
+ */
+ private void forEachConstantFragment(int start, int end, Consumer consumer) {
+ if (start == end) {
+ return;
+ }
+ if (start > end) {
+ throw new SpoonException("Inconsistent start/end. Start=" + start + " is greater then End=" + end);
+ }
+ String sourceCode = getOriginalSourceCode();
+ StringBuffer buff = new StringBuffer();
+ CharType lastType = null;
+ int off = start;
+ while (off < end) {
+ char c = sourceCode.charAt(off);
+ CharType type = CharType.fromChar(c);
+ if (type != lastType) {
+ if (lastType != null) {
+ onCharSequence(lastType, buff, consumer);
+ buff.setLength(0);
+ }
+ lastType = type;
+ }
+ buff.append(c);
+ off++;
+ }
+ onCharSequence(lastType, buff, consumer);
+ }
+
+ private void onCharSequence(CharType type, StringBuffer buff, Consumer consumer) {
+ if (type == CharType.SPACE) {
+ consumer.accept(new TokenSourceFragment(buff.toString(), TokenType.SPACE));
+ return;
+ }
+ char[] str = new char[buff.length()];
+ buff.getChars(0, buff.length(), str, 0);
+ int off = 0;
+ while (off < str.length) {
+ //detect java identifier or keyword
+ int lenOfIdentifier = detectJavaIdentifier(str, off);
+ if (lenOfIdentifier > 0) {
+ String identifier = new String(str, off, lenOfIdentifier);
+ if (javaKeywords.contains(identifier)) {
+ //it is a java keyword
+ consumer.accept(new TokenSourceFragment(identifier, TokenType.KEYWORD));
+ } else {
+ //it is a java identifier
+ consumer.accept(new TokenSourceFragment(identifier, TokenType.IDENTIFIER));
+ }
+ off += lenOfIdentifier;
+ continue;
+ }
+ //detect longest match in matchers
+ StringMatcher longestMatcher = null;
+ for (StringMatcher strMatcher : matchers) {
+ if (strMatcher.isMatch(str, off)) {
+ longestMatcher = strMatcher.getLonger(longestMatcher);
+ }
+ }
+ if (longestMatcher == null) {
+ throw new SpoonException("Unexpected source text: " + buff.toString());
+ }
+ consumer.accept(new TokenSourceFragment(longestMatcher.toString(), longestMatcher.getType()));
+ off += longestMatcher.getLength();
+ }
+ }
+
+ /**
+ * @return number of characters in buff starting from start which are java identifier
+ */
+ private int detectJavaIdentifier(char[] buff, int start) {
+ int len = buff.length;
+ int o = start;
+ if (start <= len) {
+ char c = buff[o];
+ if (Character.isJavaIdentifierStart(c)) {
+ o++;
+ while (o < len) {
+ c = buff[o];
+ if (Character.isJavaIdentifierPart(c) == false) {
+ break;
+ }
+ o++;
+ }
+ }
+ }
+ return o - start;
+ }
+
+ private String getOriginalSourceCode() {
+ CompilationUnit cu = getSourcePosition().getCompilationUnit();
+ if (cu != null) {
+ return cu.getOriginalSourceCode();
+ }
+ return null;
+ }
+
+ private static final class StringMatcher {
+ private final TokenType type;
+ private final char[] chars;
+
+ private StringMatcher(final String str, TokenType type) {
+ super();
+ this.type = type;
+ chars = str.toCharArray();
+ }
+
+ public boolean isMatch(final char[] buffer, int pos) {
+ final int len = chars.length;
+ if (pos + len > buffer.length) {
+ return false;
+ }
+ for (int i = 0; i < chars.length; i++, pos++) {
+ if (chars[i] != buffer[pos]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return new String(chars);
+ }
+
+ public int getLength() {
+ return chars.length;
+ }
+
+ public StringMatcher getLonger(StringMatcher m) {
+ if (m != null && m.getLength() > getLength()) {
+ return m;
+ }
+ return this;
+ }
+
+ public TokenType getType() {
+ return type;
+ }
+ }
+
+ /**
+ * @return role of the element of this fragment in scope of it's parent
+ */
+ public CtRole getRoleInParent() {
+ return roleHandlerInParent != null ? roleHandlerInParent.getRole() : null;
+ }
+
+ /**
+ * @return the {@link ContainerKind} of the attribute which holds the element of this fragment in it's parent
+ */
+ public ContainerKind getContainerKindInParent() {
+ if (roleHandlerInParent != null) {
+ if (roleHandlerInParent.getRole() != CtRole.COMMENT) {
+ return roleHandlerInParent.getContainerKind();
+ }
+ }
+ return ContainerKind.SINGLE;
+ }
+ /**
+ * looks for next fragment whose {@link Predicate} `test` returns true
+ * @param start - the index of first to be checked fragment
+ * @return index of found fragment, or -1 if not found
+ */
+ static int findIndexOfNextFragment(List fragments, int start, Predicate test) {
+ while (start < fragments.size()) {
+ SourceFragment fragment = fragments.get(start);
+ if (test.test(fragment)) {
+ return start;
+ }
+ start++;
+ }
+ return -1;
+ }
+
+ /**
+ * @param start the index of element with lower index which is checked and may be returned
+ * @param test a {@link Predicate}, which is evaluated for each item of `fragments` starting from last one and ending with item in index `start`
+ * @return index of found fragment, or -1 if not found
+ */
+ static int findIndexOfPreviousFragment(List fragments, int start, Predicate test) {
+ int i = fragments.size() - 1;
+ while (i >= start) {
+ if (test.test(fragments.get(i))) {
+ return i;
+ }
+ i--;
+ }
+ return -1;
+ }
+
+ /**
+ * @param predicate the {@link Predicate}, which has to be checkd for each item of {@link CollectionSourceFragment}
+ * @return {@link Predicate} which calls `predicate` for each item of {@link CollectionSourceFragment}
+ * Returned {@link Predicate} returns true only if `predicate` returns true on at least one item
+ */
+ static Predicate checkCollectionItems(Predicate predicate) {
+ return (SourceFragment fragment) -> {
+ if (fragment instanceof CollectionSourceFragment) {
+ CollectionSourceFragment collectionFragment = (CollectionSourceFragment) fragment;
+ for (SourceFragment itemFragment : collectionFragment.getItems()) {
+ if (predicate.test(itemFragment)) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return predicate.test(fragment);
+ }
+ };
+ }
+
+ /**
+ * @param predicate to be called {@link Predicate}
+ * @return {@link Predicate} which calls `predicate` only for {@link SourceFragment}s of of type `clazz` and returns false for others
+ */
+ static Predicate filter(Class clazz, Predicate predicate) {
+ return fragment -> {
+ if (clazz.isInstance(fragment)) {
+ return predicate.test((T) fragment);
+ }
+ return false;
+ };
+ }
+
+ /**
+ * @return true if {@link SourceFragment} represents a white space
+ */
+ static boolean isSpaceFragment(SourceFragment fragment) {
+ return fragment instanceof TokenSourceFragment && ((TokenSourceFragment) fragment).getType() == TokenType.SPACE;
+ }
+}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java
new file mode 100644
index 00000000000..01ef9487a6e
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.reflect.visitor.printer.change;
+
+/**
+ */
+public interface SourceFragment {
+ /**
+ * @return origin source code of whole fragment represented by this instance
+ */
+ String getSourceCode();
+}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java
new file mode 100644
index 00000000000..1978d926af8
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.reflect.visitor.printer.change;
+
+/**
+ * a {@link SourceFragment} of some primitive String token.
+ */
+public class TokenSourceFragment implements SourceFragment {
+
+ private final String source;
+ private final TokenType type;
+
+ public TokenSourceFragment(String source, TokenType type) {
+ super();
+ this.source = source;
+ this.type = type;
+ }
+
+ @Override
+ public String getSourceCode() {
+ return source;
+ }
+
+ /**
+ * @return type of token of this fragment
+ */
+ public TokenType getType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return "|" + getSourceCode() + "|";
+ }
+}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java b/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
new file mode 100644
index 00000000000..2a4b0462362
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
@@ -0,0 +1,30 @@
+package spoon.reflect.visitor.printer.change;
+
+enum TokenType {
+
+ SEPARATOR(false, false),
+ OPERATOR(false, false),
+ LITERAL(false, false),
+ KEYWORD(false, false),
+ IDENTIFIER(false, false),
+ CODE_SNIPPET(false, false),
+ COMMENT(false, false),
+ NEW_LINE(true, false),
+ INC_TAB(true, true),
+ DEC_TAB(true, true),
+ SPACE(true, false);
+
+ private final boolean whiteSpace;
+ private final boolean tab;
+
+ TokenType(boolean whiteSpace, boolean tab) {
+ this.whiteSpace = whiteSpace;
+ this.tab = tab;
+ }
+ boolean isWhiteSpace() {
+ return whiteSpace;
+ }
+ public boolean isTab() {
+ return tab;
+ }
+}
diff --git a/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java b/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
index 833b6117440..f7e35a816f4 100644
--- a/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
+++ b/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
@@ -16,16 +16,19 @@
*/
package spoon.support.reflect.cu;
+import spoon.SpoonException;
import spoon.processing.FactoryAccessor;
import spoon.reflect.cu.CompilationUnit;
import spoon.reflect.cu.SourcePosition;
+import spoon.reflect.cu.SourcePositionHolder;
+import spoon.reflect.declaration.CtImport;
import spoon.reflect.declaration.CtModule;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
-import spoon.reflect.declaration.CtImport;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.filter.TypeFilter;
+import spoon.reflect.visitor.printer.change.ElementSourceFragment;
import spoon.support.reflect.cu.position.PartialSourcePositionImpl;
import java.io.File;
@@ -65,6 +68,8 @@ public class CompilationUnitImpl implements CompilationUnit, FactoryAccessor {
*/
private int[] lineSeparatorPositions;
+ private ElementSourceFragment rootFragment;
+
@Override
public UNIT_TYPE getUnitType() {
// we try to guess based on the file name
@@ -301,6 +306,31 @@ public SourcePosition getOrCreatePartialSourcePosition() {
return myPartialSourcePosition;
}
+ @Override
+ public ElementSourceFragment getRootSourceFragment() {
+ if (rootFragment == null) {
+ String originSourceCode = getOriginalSourceCode();
+ rootFragment = new ElementSourceFragment(this, null);
+ for (CtImport imprt : getImports()) {
+ rootFragment.addChild(new ElementSourceFragment(imprt, null /*TODO role for import of CU*/));
+ }
+ for (CtType> ctType : declaredTypes) {
+ rootFragment.addTreeOfSourceFragmentsOfElement(ctType);
+ }
+ }
+ return rootFragment;
+ }
+
+ @Override
+ public ElementSourceFragment getSourceFragment(SourcePositionHolder element) {
+ ElementSourceFragment rootFragment = getRootSourceFragment();
+ SourcePosition sp = element.getPosition();
+ if (sp.getCompilationUnit() != this) {
+ throw new SpoonException("Cannot return SourceFragment of element for different CompilationUnit");
+ }
+ return rootFragment.getSourceFragmentOf(element, sp.getSourceStart(), sp.getSourceEnd() + 1);
+ }
+
@Override
public int[] getLineSeparatorPositions() {
return lineSeparatorPositions;
diff --git a/src/test/java/spoon/MavenLauncherTest.java b/src/test/java/spoon/MavenLauncherTest.java
index f3f1b66aacf..66136f31dc6 100644
--- a/src/test/java/spoon/MavenLauncherTest.java
+++ b/src/test/java/spoon/MavenLauncherTest.java
@@ -47,15 +47,15 @@ public void spoonMavenLauncherTest() {
assertEquals(190, launcher.getEnvironment().getSourceClasspath().length);
- // 56 because of the sub folders of src/main/java
- assertEquals(59, launcher.getModelBuilder().getInputSources().size());
+ // number of the sub folders of src/main/java
+ assertEquals(60, launcher.getModelBuilder().getInputSources().size());
// with the tests
launcher = new MavenLauncher("./", MavenLauncher.SOURCE_TYPE.ALL_SOURCE);
assertEquals(195, launcher.getEnvironment().getSourceClasspath().length);
- // 236 because of the sub folders of src/main/java and src/test/java
+ // number of the sub folders of src/main/java and src/test/java
assertTrue("size: " + launcher.getModelBuilder().getInputSources().size(), launcher.getModelBuilder().getInputSources().size() >= 220);
// specify the pom.xml
diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java
index 6fd64495e94..f557250bea4 100644
--- a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java
+++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java
@@ -365,6 +365,7 @@ public void testSpecPackage() {
officialPackages.add("spoon.reflect.visitor.chain");
officialPackages.add("spoon.reflect.visitor.filter");
officialPackages.add("spoon.reflect.visitor.printer");
+ officialPackages.add("spoon.reflect.visitor.printer.change");
officialPackages.add("spoon.reflect.visitor");
officialPackages.add("spoon.reflect");
officialPackages.add("spoon.support.comparator");
diff --git a/src/test/java/spoon/test/main/MainTest.java b/src/test/java/spoon/test/main/MainTest.java
index dfbfca6fe01..bd45143f120 100644
--- a/src/test/java/spoon/test/main/MainTest.java
+++ b/src/test/java/spoon/test/main/MainTest.java
@@ -40,6 +40,7 @@
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.PrinterHelper;
import spoon.reflect.visitor.filter.TypeFilter;
+import spoon.reflect.visitor.printer.change.ElementSourceFragment;
import spoon.support.reflect.CtExtendedModifier;
import spoon.test.parent.ParentTest;
@@ -53,6 +54,7 @@
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -95,7 +97,8 @@ public static void loadModel() {
"--source-classpath", systemClassPath,
"--compile", // compiling Spoon code itself on the fly
"--compliance", "8",
- "--level", "OFF"
+ "--level", "OFF",
+ "--enable-comments"
});
launcher.buildModel();
@@ -471,6 +474,39 @@ public void scan(CtRole role, CtElement element) {
});
}
+ @Test
+ public void testSourcePositionTreeIsCorrectlyOrdered() {
+ List types = rootPackage.filterChildren(new TypeFilter<>(CtType.class)).filterChildren((CtType t) -> t.isTopLevel()).list();
+ int totalCount = 0;
+ boolean hasComment = false;
+ for (CtType type : types) {
+ SourcePosition sp = type.getPosition();
+ totalCount += assertSourcePositionTreeIsCorrectlyOrder(sp.getCompilationUnit().getRootSourceFragment());
+ hasComment = hasComment || type.getComments().size() > 0;
+ };
+ assertTrue(totalCount > 1000);
+ assertTrue(hasComment);
+ }
+
+ /**
+ * Asserts that all siblings and children of sp are well ordered
+ * @param sourceFragment
+ * @return number of checked {@link SourcePosition} nodes
+ */
+ private int assertSourcePositionTreeIsCorrectlyOrder(ElementSourceFragment sourceFragment) {
+ int nr = 0;
+ int pos = 0;
+ while (sourceFragment != null) {
+ nr++;
+ assertTrue(pos <= sourceFragment.getStart());
+ assertTrue(sourceFragment.getStart() <= sourceFragment.getEnd());
+ pos = sourceFragment.getEnd();
+ nr += assertSourcePositionTreeIsCorrectlyOrder(sourceFragment.getFirstChild());
+ sourceFragment = sourceFragment.getNextSibling();
+ }
+ return nr;
+ }
+
@Test
public void testElementToPathToElementEquivalency() {
diff --git a/src/test/java/spoon/test/position/TestSourceFragment.java b/src/test/java/spoon/test/position/TestSourceFragment.java
new file mode 100644
index 00000000000..f8863053e8e
--- /dev/null
+++ b/src/test/java/spoon/test/position/TestSourceFragment.java
@@ -0,0 +1,223 @@
+package spoon.test.position;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.Test;
+
+import spoon.Launcher;
+import spoon.SpoonModelBuilder;
+import spoon.compiler.SpoonResourceHelper;
+import spoon.reflect.code.CtAssignment;
+import spoon.reflect.cu.CompilationUnit;
+import spoon.reflect.cu.SourcePosition;
+import spoon.reflect.declaration.CtElement;
+import spoon.reflect.declaration.CtType;
+import spoon.reflect.factory.Factory;
+import spoon.reflect.visitor.printer.change.SourceFragment;
+import spoon.reflect.visitor.printer.change.CollectionSourceFragment;
+import spoon.reflect.visitor.printer.change.ElementSourceFragment;
+import spoon.support.reflect.cu.CompilationUnitImpl;
+import spoon.support.reflect.cu.position.SourcePositionImpl;
+import spoon.test.position.testclasses.FooSourceFragments;
+
+public class TestSourceFragment {
+
+ @Test
+ public void testSourcePositionFragment() throws Exception {
+ SourcePosition sp = new SourcePositionImpl(DUMMY_COMPILATION_UNIT, 10, 20, null);
+ ElementSourceFragment sf = new ElementSourceFragment(() -> sp, null);
+ assertEquals(10, sf.getStart());
+ assertEquals(21, sf.getEnd());
+ assertSame(sp, sf.getSourcePosition());
+ assertNull(sf.getFirstChild());
+ assertNull(sf.getNextSibling());
+
+ }
+
+ @Test
+ public void testSourceFragmentAddChild() throws Exception {
+ //contract: check build of the tree of SourceFragments
+ ElementSourceFragment rootFragment = createFragment(10, 20);
+ ElementSourceFragment f;
+ //add child
+ assertSame(rootFragment, rootFragment.add(f = createFragment(10, 15)));
+ assertSame(rootFragment.getFirstChild(), f);
+
+ //add child which is next sibling of first child
+ assertSame(rootFragment, rootFragment.add(f = createFragment(15, 20)));
+ assertSame(rootFragment.getFirstChild().getNextSibling(), f);
+
+ //add another child of same start/end, which has to be child of last child
+ assertSame(rootFragment, rootFragment.add(f = createFragment(15, 20)));
+ assertSame(rootFragment.getFirstChild().getNextSibling().getFirstChild(), f);
+
+ //add another child of smaller start/end, which has to be child of last child
+ assertSame(rootFragment, rootFragment.add(f = createFragment(16, 20)));
+ assertSame(rootFragment.getFirstChild().getNextSibling().getFirstChild().getFirstChild(), f);
+
+ //add next sibling of root element
+ assertSame(rootFragment, rootFragment.add(f = createFragment(20, 100)));
+ assertSame(rootFragment.getNextSibling(), f);
+
+ //add prev sibling of root element. We should get new root
+ f = createFragment(5, 10);
+ assertSame(f, rootFragment.add(f));
+ assertSame(f.getNextSibling(), rootFragment);
+ }
+
+ @Test
+ public void testSourceFragmentAddChildBeforeOrAfter() throws Exception {
+ //contract: start / end of root fragment is moved when child is added
+ ElementSourceFragment rootFragment = createFragment(10, 20);
+ rootFragment.addChild(createFragment(5, 7));
+ assertEquals(5, rootFragment.getStart());
+ assertEquals(20, rootFragment.getEnd());
+ rootFragment.addChild(createFragment(20, 25));
+ assertEquals(5, rootFragment.getStart());
+ assertEquals(25, rootFragment.getEnd());
+ }
+
+ @Test
+ public void testSourceFragmentWrapChild() throws Exception {
+ //contract: the existing child fragment can be wrapped by a new parent
+ ElementSourceFragment rootFragment = createFragment(0, 100);
+ ElementSourceFragment child = createFragment(50, 60);
+ rootFragment.add(child);
+
+ ElementSourceFragment childWrapper = createFragment(40, 60);
+ rootFragment.add(childWrapper);
+ assertSame(rootFragment.getFirstChild(), childWrapper);
+ assertSame(rootFragment.getFirstChild().getFirstChild(), child);
+ }
+ @Test
+ public void testSourceFragmentWrapChildrenAndSiblings() throws Exception {
+ //contract: the two SourceFragment trees merge correctly together
+ ElementSourceFragment rootFragment = createFragment(0, 100);
+ ElementSourceFragment child = createFragment(50, 60);
+ rootFragment.add(child);
+
+ ElementSourceFragment childWrapper = createFragment(40, 70);
+ ElementSourceFragment childA = createFragment(40, 50);
+ ElementSourceFragment childB = createFragment(50, 55);
+ ElementSourceFragment childC = createFragment(60, 65);
+ ElementSourceFragment childD = createFragment(65, 70);
+ //add all children and check the root is still childWrapper
+ assertSame(childWrapper, childWrapper.add(childA).add(childB).add(childC).add(childD));
+ //add childWrapper which has to merge with before added child, because childWrapper is parent of child
+ rootFragment.add(childWrapper);
+ assertSame(rootFragment.getFirstChild(), childWrapper);
+ assertSame(childA, childWrapper.getFirstChild());
+ assertSame(child, childA.getNextSibling());
+ assertSame(childB, child.getFirstChild());
+ assertSame(childC, child.getNextSibling());
+ assertSame(childD, childC.getNextSibling());
+ }
+
+ @Test
+ public void testElementSourceFragment_getSourceFragmentOf() throws Exception {
+ //contract: ElementSourceFragment#getSourceFragmentOf returns expected results
+ ElementSourceFragment rootFragment = createFragment(0, 100);
+ ElementSourceFragment x;
+ rootFragment.add(createFragment(50, 60));
+ rootFragment.add(createFragment(60, 70));
+ rootFragment.add(x = createFragment(50, 55));
+
+ assertSame(x, rootFragment.getSourceFragmentOf(null, 50, 55));
+ assertSame(rootFragment, rootFragment.getSourceFragmentOf(null, 0, 100));
+ assertSame(rootFragment.getFirstChild(), rootFragment.getSourceFragmentOf(null, 50, 60));
+ assertSame(rootFragment.getFirstChild().getNextSibling(), rootFragment.getSourceFragmentOf(null, 60, 70));
+ }
+
+ private static final CompilationUnit DUMMY_COMPILATION_UNIT = new CompilationUnitImpl();
+
+ private ElementSourceFragment createFragment(int start, int end) {
+ SourcePosition sp = new SourcePositionImpl(DUMMY_COMPILATION_UNIT, start, end - 1, null);
+ return new ElementSourceFragment(() -> sp, null);
+ }
+
+ @Test
+ public void testSourceFragmentOfIf() throws Exception {
+ //contract: SourceFragments of some tricky sources are as expected
+ final Launcher launcher = new Launcher();
+ launcher.getEnvironment().setNoClasspath(false);
+ launcher.getEnvironment().setCommentEnabled(true);
+ SpoonModelBuilder comp = launcher.createCompiler();
+ comp.addInputSources(SpoonResourceHelper.resources("./src/test/java/" + FooSourceFragments.class.getName().replace('.', '/') + ".java"));
+ comp.build();
+ Factory f = comp.getFactory();
+
+ final CtType> foo = f.Type().get(FooSourceFragments.class);
+ checkElementFragments(foo.getMethodsByName("m1").get(0).getBody().getStatement(0),
+ "if", "(", "x > 0", ")", "{this.getClass();}", "else", "{/*empty*/}");
+ checkElementFragments(foo.getMethodsByName("m2").get(0).getBody().getStatement(0),
+ "/*c0*/", " ", "if", " ", "/*c1*/", "\t", "(", " ", "//c2", "\n\t\t\t\t", "x > 0", " ", "/*c3*/", " ", ")", " ", "/*c4*/", " ", "{ \n" +
+ " this.getClass();\n" +
+ " }", " ", "/*c5*/ else /*c6*/ {\n" +
+ " /*empty*/\n" +
+ " }", " ", "/*c7*/");
+ checkElementFragments(foo.getMethodsByName("m3").get(0),
+ "/**\n" +
+ " * c0\n" +
+ " */",
+ group("\n\t", "public", "\n\t", "@Deprecated", " ", "//c1 ends with tab and space\t ", "\n\t", "static", " "), "/*c2*/", " ",
+ "<", group("T", ",", " ", "U"), ">",
+ " ", "T", " ", "m3", "(", group("U param", ",", " ", "@Deprecated int p2"), ")", " ", "{\n" +
+ " return null;\n" +
+ " }");
+ //TODO uncomment and fix source code of element label
+ //checkElementFragments(foo.getMethodsByName("m4").get(0).getBody().getStatement(0),"");
+
+ checkElementFragments(foo.getMethodsByName("m5").get(0).getBody().getStatement(0),"f", " ", "=", " ", "7.2", ";");
+ checkElementFragments(((CtAssignment)foo.getMethodsByName("m5").get(0).getBody().getStatement(0)).getAssignment(),"7.2");
+
+ }
+
+ private void checkElementFragments(CtElement ele, Object... expectedFragments) {
+ ElementSourceFragment fragment = ele.getPosition().getCompilationUnit().getSourceFragment(ele);
+ List children = fragment.getChildrenFragments();
+ assertEquals(expandGroup(new ArrayList<>(), expectedFragments), childSourceFragmentsToStrings(children));
+ assertGroupsEqual(expectedFragments, fragment.getGroupedChildrenFragments());
+ }
+
+ private String[] group(String ...str) {
+ return str;
+ }
+
+ private List expandGroup(List result, Object[] items) {
+ for (Object object : items) {
+ if (object instanceof String[]) {
+ String[] strings = (String[]) object;
+ expandGroup(result, strings);
+ } else {
+ result.add((String) object);
+ }
+ }
+ return result;
+ }
+
+ private static void assertGroupsEqual(Object[] expectedFragments, List groupedChildrenFragments) {
+ assertEquals(Arrays.stream(expectedFragments).map(item->{
+ if (item instanceof String[]) {
+ return "group("+Arrays.asList((String[]) item).toString() + ")";
+ }
+ return item;
+ }).collect(Collectors.toList()), groupedChildrenFragments.stream().map(item -> {
+ if (item instanceof CollectionSourceFragment) {
+ CollectionSourceFragment csf = (CollectionSourceFragment) item;
+ return "group("+childSourceFragmentsToStrings(csf.getItems()).toString() + ")";
+ }
+ return item.getSourceCode();
+ }).collect(Collectors.toList()));
+ }
+
+ private static List childSourceFragmentsToStrings(List csf) {
+ return csf.stream().map(SourceFragment::getSourceCode).collect(Collectors.toList());
+ }
+}
diff --git a/src/test/java/spoon/test/position/testclasses/FooSourceFragments.java b/src/test/java/spoon/test/position/testclasses/FooSourceFragments.java
new file mode 100644
index 00000000000..5b3deb9cdf4
--- /dev/null
+++ b/src/test/java/spoon/test/position/testclasses/FooSourceFragments.java
@@ -0,0 +1,32 @@
+package spoon.test.position.testclasses;
+
+public class FooSourceFragments {
+ void m1(int x) {
+ if(x > 0){this.getClass();}else{/*empty*/}
+ }
+ void m2(int x) {
+ /*c0*/ if /*c1*/ ( //c2
+ x > 0 /*c3*/ ) /*c4*/ {
+ this.getClass();
+ } /*c5*/ else /*c6*/ {
+ /*empty*/
+ } /*c7*/
+ }
+ /**
+ * c0
+ */
+ public
+ @Deprecated //c1 ends with tab and space
+ static /*c2*/ T m3(U param, @Deprecated int p2) {
+ return null;
+ }
+
+ void m4() {
+ label: while(true);
+ }
+
+ void m5(double f) {
+ f = 7.2;
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/spoon/test/position/testclasses/MethodWithJavaDocAndModifiers.java b/src/test/java/spoon/test/position/testclasses/MethodWithJavaDocAndModifiers.java
new file mode 100644
index 00000000000..56c9dec7c9f
--- /dev/null
+++ b/src/test/java/spoon/test/position/testclasses/MethodWithJavaDocAndModifiers.java
@@ -0,0 +1,12 @@
+package spoon.test.position.testclasses;
+
+public class MethodWithJavaDocAndModifiers {
+
+ /**
+ * Method with javadoc
+ * @param parm1 the parameter
+ */
+ public @Deprecated int mWithDoc(int parm1) {
+ return parm1;
+ }
+}
\ No newline at end of file
From 08efff0cdc8bc023e39d035909c55d81c5a6312c Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Tue, 21 Aug 2018 19:54:09 +0200
Subject: [PATCH 02/16] TokenType is public
---
.../spoon/reflect/visitor/printer/change/TokenType.java | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java b/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
index 2a4b0462362..d0b9f3b48d7 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
@@ -1,6 +1,12 @@
package spoon.reflect.visitor.printer.change;
-enum TokenType {
+import spoon.reflect.visitor.TokenWriter;
+
+/**
+ * Type of {@link TokenSourceFragment} token.
+ * Note: These types mirrors the methods of {@link TokenWriter}
+ */
+public enum TokenType {
SEPARATOR(false, false),
OPERATOR(false, false),
From 5e0d36d75f5c36fd8b431fea0e614b7e3c8ae67b Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Tue, 21 Aug 2018 21:03:28 +0200
Subject: [PATCH 03/16] fix license headers
---
.../printer/change/CollectionSourceFragment.java | 2 +-
.../printer/change/ElementSourceFragment.java | 2 +-
.../visitor/printer/change/SourceFragment.java | 2 +-
.../printer/change/TokenSourceFragment.java | 2 +-
.../visitor/printer/change/TokenType.java | 16 ++++++++++++++++
5 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java
index 4aab9201213..1a74272be50 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2006-2017 INRIA and contributors
+ * Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
index 50bcf993d91..c0da0eae360 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2006-2017 INRIA and contributors
+ * Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java
index 01ef9487a6e..459f813e0f5 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2006-2017 INRIA and contributors
+ * Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java
index 1978d926af8..c394ae5f355 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2006-2017 INRIA and contributors
+ * Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java b/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
index d0b9f3b48d7..cb05e317a8d 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
@@ -1,3 +1,19 @@
+/**
+ * Copyright (C) 2006-2018 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
package spoon.reflect.visitor.printer.change;
import spoon.reflect.visitor.TokenWriter;
From 4376d2a9a10bd57f5a94e628e6c9510c608cdf53 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Mon, 27 Aug 2018 20:37:04 +0200
Subject: [PATCH 04/16] fail when getRootSourceFragment is called on package or
module
---
.../java/spoon/support/reflect/cu/CompilationUnitImpl.java | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java b/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
index f7e35a816f4..68dce1527fd 100644
--- a/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
+++ b/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
@@ -309,7 +309,12 @@ public SourcePosition getOrCreatePartialSourcePosition() {
@Override
public ElementSourceFragment getRootSourceFragment() {
if (rootFragment == null) {
- String originSourceCode = getOriginalSourceCode();
+ if (ctModule != null) {
+ throw new SpoonException("Root source fragment of compilation unit of module is not supported");
+ }
+ if (ctPackage != null && declaredTypes.isEmpty()) {
+ throw new SpoonException("Root source fragment of compilation unit of package is not supported");
+ }
rootFragment = new ElementSourceFragment(this, null);
for (CtImport imprt : getImports()) {
rootFragment.addChild(new ElementSourceFragment(imprt, null /*TODO role for import of CU*/));
From e05fbb3efc21e01c51258391191b2b92041a4376 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Mon, 27 Aug 2018 20:37:26 +0200
Subject: [PATCH 05/16] contract and improvement of
testSourcePositionTreeIsCorrectlyOrdered
---
src/test/java/spoon/test/main/MainTest.java | 22 +++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/src/test/java/spoon/test/main/MainTest.java b/src/test/java/spoon/test/main/MainTest.java
index bd45143f120..12fe14b4527 100644
--- a/src/test/java/spoon/test/main/MainTest.java
+++ b/src/test/java/spoon/test/main/MainTest.java
@@ -476,12 +476,19 @@ public void scan(CtRole role, CtElement element) {
@Test
public void testSourcePositionTreeIsCorrectlyOrdered() {
+ /*
+ * contract: the tree of ElementSourceFragments of all spoon types (= sample set of sources) can be built.
+ * contract: the tree of ElementSourceFragments is correctly organized. It means:
+ * - source positions of children elements are smaller or equal to their parents
+ * - source positions of next siblings are after their previous siblings
+ * -
+ */
List types = rootPackage.filterChildren(new TypeFilter<>(CtType.class)).filterChildren((CtType t) -> t.isTopLevel()).list();
int totalCount = 0;
boolean hasComment = false;
for (CtType type : types) {
SourcePosition sp = type.getPosition();
- totalCount += assertSourcePositionTreeIsCorrectlyOrder(sp.getCompilationUnit().getRootSourceFragment());
+ totalCount += assertSourcePositionTreeIsCorrectlyOrder(sp.getCompilationUnit().getRootSourceFragment(), 0, sp.getCompilationUnit().getOriginalSourceCode().length());
hasComment = hasComment || type.getComments().size() > 0;
};
assertTrue(totalCount > 1000);
@@ -491,19 +498,22 @@ public void testSourcePositionTreeIsCorrectlyOrdered() {
/**
* Asserts that all siblings and children of sp are well ordered
* @param sourceFragment
+ * @param minOffset TODO
+ * @param maxOffset TODO
* @return number of checked {@link SourcePosition} nodes
*/
- private int assertSourcePositionTreeIsCorrectlyOrder(ElementSourceFragment sourceFragment) {
+ private int assertSourcePositionTreeIsCorrectlyOrder(ElementSourceFragment sourceFragment, int minOffset, int maxOffset) {
int nr = 0;
- int pos = 0;
+ int pos = minOffset;
while (sourceFragment != null) {
nr++;
- assertTrue(pos <= sourceFragment.getStart());
- assertTrue(sourceFragment.getStart() <= sourceFragment.getEnd());
+ assertTrue("min(" + pos + ") <= fragment.start(" + sourceFragment.getStart() + ")", pos <= sourceFragment.getStart());
+ assertTrue("fragment.start(" + sourceFragment.getStart() + ") <= fragment.end(" + sourceFragment.getEnd() + ")", sourceFragment.getStart() <= sourceFragment.getEnd());
pos = sourceFragment.getEnd();
- nr += assertSourcePositionTreeIsCorrectlyOrder(sourceFragment.getFirstChild());
+ nr += assertSourcePositionTreeIsCorrectlyOrder(sourceFragment.getFirstChild(), sourceFragment.getStart(), sourceFragment.getEnd());
sourceFragment = sourceFragment.getNextSibling();
}
+ assertTrue("lastFragment.end(" + pos + ") <= max(" + maxOffset + ")", pos <= maxOffset);
return nr;
}
From 918aa77d22e160aa03da268a291c6a52308070b2 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Thu, 6 Sep 2018 18:34:51 +0200
Subject: [PATCH 06/16] apply #2283
---
.../java/spoon/reflect/cu/CompilationUnit.java | 17 -----------------
.../spoon/reflect/cu/SourcePositionHolder.java | 13 ++++++++++++-
.../printer/change/ElementSourceFragment.java | 7 ++++++-
.../support/reflect/CtExtendedModifier.java | 14 ++++++++++++++
.../support/reflect/cu/CompilationUnitImpl.java | 13 +------------
.../reflect/declaration/CtElementImpl.java | 14 ++++++++++++++
src/test/java/spoon/test/main/MainTest.java | 2 +-
.../spoon/test/position/TestSourceFragment.java | 2 +-
8 files changed, 49 insertions(+), 33 deletions(-)
diff --git a/src/main/java/spoon/reflect/cu/CompilationUnit.java b/src/main/java/spoon/reflect/cu/CompilationUnit.java
index fed60343a1e..c92cea03687 100644
--- a/src/main/java/spoon/reflect/cu/CompilationUnit.java
+++ b/src/main/java/spoon/reflect/cu/CompilationUnit.java
@@ -20,8 +20,6 @@
import spoon.reflect.declaration.CtModule;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtType;
-import spoon.reflect.visitor.printer.change.ElementSourceFragment;
-import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtImport;
import spoon.support.Experimental;
@@ -166,19 +164,4 @@ enum UNIT_TYPE {
*/
@Experimental
void setImports(Set imports);
-
- /**
- * @return root {@link ElementSourceFragment} of the tree of all the source fragments of origin source code of this compilation unit
- * Note: this method creates tree of {@link ElementSourceFragment}s ... it can be a lot of instances
- * If the tree of {@link ElementSourceFragment}s is needed then it MUST be created
- * BEFORE the Spoon model of this {@link CompilationUnit} is changed. The best creation time is immediately after Spoon model is built.
- * Otherwise there might be missing some {@link ElementSourceFragment}s when {@link CtElement}s are deleted
- */
- ElementSourceFragment getRootSourceFragment();
-
- /**
- * @param element the {@link CtElement} whose origin source code is needed.
- * @return {@link ElementSourceFragment} which mirrors the origin source code of the `element` or null.
- */
- ElementSourceFragment getSourceFragment(SourcePositionHolder element);
}
diff --git a/src/main/java/spoon/reflect/cu/SourcePositionHolder.java b/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
index 231a4b56081..de4a4b367bd 100644
--- a/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
+++ b/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
@@ -16,9 +16,20 @@
*/
package spoon.reflect.cu;
+import spoon.reflect.visitor.printer.change.ElementSourceFragment;
+
/**
- * This interface represents an element which knows it's position in a source file.
+ * This interface represents an element which knows its position in a source file.
*/
public interface SourcePositionHolder {
+ /** If the element comes from a Java source file (hence has not created during transformation), returns the position in the original source file */
SourcePosition getPosition();
+
+ /**
+ * Returns the original source code (maybe different from toString() if a transformation has been applied.
+ * Or {@link ElementSourceFragment#NO_SOURCE_FRAGMENT} if this element has no original source fragment.
+ */
+ default ElementSourceFragment getOriginalSourceFragment() {
+ return ElementSourceFragment.NO_SOURCE_FRAGMENT;
+ }
}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
index c0da0eae360..6e2a0ea3efe 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
@@ -47,11 +47,16 @@
/**
* Represents a part of source code of an {@link CtElement}
* It is connected into a tree of {@link ElementSourceFragment}s.
- * That tree can be build by {@link CompilationUnit#getRootSourceFragment()}
+ * That tree can be build by {@link CompilationUnit#getOriginalSourceFragment()}
* And the tree of {@link ElementSourceFragment}s related to one element can be returned by {@link CompilationUnit#getSourceFragment(SourcePositionHolder)}
*/
public class ElementSourceFragment implements SourceFragment {
+ /**
+ * represents an {@link ElementSourceFragment}, which doesn't exist
+ */
+ public static final ElementSourceFragment NO_SOURCE_FRAGMENT = new ElementSourceFragment(null, null);
+
private final SourcePositionHolder element;
private final RoleHandler roleHandlerInParent;
private ElementSourceFragment nextSibling;
diff --git a/src/main/java/spoon/support/reflect/CtExtendedModifier.java b/src/main/java/spoon/support/reflect/CtExtendedModifier.java
index db8090a783f..d8bd7beb2b4 100644
--- a/src/main/java/spoon/support/reflect/CtExtendedModifier.java
+++ b/src/main/java/spoon/support/reflect/CtExtendedModifier.java
@@ -16,9 +16,11 @@
*/
package spoon.support.reflect;
+import spoon.reflect.cu.CompilationUnit;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.cu.SourcePositionHolder;
import spoon.reflect.declaration.ModifierKind;
+import spoon.reflect.visitor.printer.change.ElementSourceFragment;
import java.io.Serializable;
@@ -87,4 +89,16 @@ public int hashCode() {
result = 31 * result + (kind != null ? kind.hashCode() : 0);
return result;
}
+
+ @Override
+ public ElementSourceFragment getOriginalSourceFragment() {
+ SourcePosition sp = this.getPosition();
+ CompilationUnit compilationUnit = sp.getCompilationUnit();
+ if (compilationUnit != null) {
+ ElementSourceFragment rootFragment = (ElementSourceFragment) compilationUnit.getOriginalSourceFragment();
+ return rootFragment.getSourceFragmentOf(this, sp.getSourceStart(), sp.getSourceEnd() + 1);
+ } else {
+ return ElementSourceFragment.NO_SOURCE_FRAGMENT;
+ }
+ }
}
diff --git a/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java b/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
index 68dce1527fd..945e21e6763 100644
--- a/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
+++ b/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
@@ -20,7 +20,6 @@
import spoon.processing.FactoryAccessor;
import spoon.reflect.cu.CompilationUnit;
import spoon.reflect.cu.SourcePosition;
-import spoon.reflect.cu.SourcePositionHolder;
import spoon.reflect.declaration.CtImport;
import spoon.reflect.declaration.CtModule;
import spoon.reflect.declaration.CtPackage;
@@ -307,7 +306,7 @@ public SourcePosition getOrCreatePartialSourcePosition() {
}
@Override
- public ElementSourceFragment getRootSourceFragment() {
+ public ElementSourceFragment getOriginalSourceFragment() {
if (rootFragment == null) {
if (ctModule != null) {
throw new SpoonException("Root source fragment of compilation unit of module is not supported");
@@ -326,16 +325,6 @@ public ElementSourceFragment getRootSourceFragment() {
return rootFragment;
}
- @Override
- public ElementSourceFragment getSourceFragment(SourcePositionHolder element) {
- ElementSourceFragment rootFragment = getRootSourceFragment();
- SourcePosition sp = element.getPosition();
- if (sp.getCompilationUnit() != this) {
- throw new SpoonException("Cannot return SourceFragment of element for different CompilationUnit");
- }
- return rootFragment.getSourceFragmentOf(element, sp.getSourceStart(), sp.getSourceEnd() + 1);
- }
-
@Override
public int[] getLineSeparatorPositions() {
return lineSeparatorPositions;
diff --git a/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java b/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java
index dec6a207d7f..1b32c409a57 100644
--- a/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java
+++ b/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java
@@ -24,6 +24,7 @@
import spoon.reflect.code.CtComment;
import spoon.reflect.code.CtJavaDoc;
import spoon.reflect.code.CtJavaDocTag;
+import spoon.reflect.cu.CompilationUnit;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtElement;
@@ -52,6 +53,7 @@
import spoon.reflect.visitor.chain.CtFunction;
import spoon.reflect.visitor.chain.CtQuery;
import spoon.reflect.visitor.filter.AnnotationFilter;
+import spoon.reflect.visitor.printer.change.ElementSourceFragment;
import spoon.reflect.visitor.CtIterator;
import spoon.support.DefaultCoreFactory;
import spoon.support.DerivedProperty;
@@ -612,4 +614,16 @@ public Iterator descendantIterator() {
public Iterable asIterable() {
return this::descendantIterator;
}
+
+ @Override
+ public ElementSourceFragment getOriginalSourceFragment() {
+ SourcePosition sp = this.getPosition();
+ CompilationUnit compilationUnit = sp.getCompilationUnit();
+ if (compilationUnit != null) {
+ ElementSourceFragment rootFragment = (ElementSourceFragment) compilationUnit.getOriginalSourceFragment();
+ return rootFragment.getSourceFragmentOf(this, sp.getSourceStart(), sp.getSourceEnd() + 1);
+ } else {
+ return ElementSourceFragment.NO_SOURCE_FRAGMENT;
+ }
+ }
}
diff --git a/src/test/java/spoon/test/main/MainTest.java b/src/test/java/spoon/test/main/MainTest.java
index 12fe14b4527..75c24c8a543 100644
--- a/src/test/java/spoon/test/main/MainTest.java
+++ b/src/test/java/spoon/test/main/MainTest.java
@@ -488,7 +488,7 @@ public void testSourcePositionTreeIsCorrectlyOrdered() {
boolean hasComment = false;
for (CtType type : types) {
SourcePosition sp = type.getPosition();
- totalCount += assertSourcePositionTreeIsCorrectlyOrder(sp.getCompilationUnit().getRootSourceFragment(), 0, sp.getCompilationUnit().getOriginalSourceCode().length());
+ totalCount += assertSourcePositionTreeIsCorrectlyOrder(sp.getCompilationUnit().getOriginalSourceFragment(), 0, sp.getCompilationUnit().getOriginalSourceCode().length());
hasComment = hasComment || type.getComments().size() > 0;
};
assertTrue(totalCount > 1000);
diff --git a/src/test/java/spoon/test/position/TestSourceFragment.java b/src/test/java/spoon/test/position/TestSourceFragment.java
index f8863053e8e..88ed5f534f5 100644
--- a/src/test/java/spoon/test/position/TestSourceFragment.java
+++ b/src/test/java/spoon/test/position/TestSourceFragment.java
@@ -180,7 +180,7 @@ public void testSourceFragmentOfIf() throws Exception {
}
private void checkElementFragments(CtElement ele, Object... expectedFragments) {
- ElementSourceFragment fragment = ele.getPosition().getCompilationUnit().getSourceFragment(ele);
+ ElementSourceFragment fragment = ele.getOriginalSourceFragment();
List children = fragment.getChildrenFragments();
assertEquals(expandGroup(new ArrayList<>(), expectedFragments), childSourceFragmentsToStrings(children));
assertGroupsEqual(expectedFragments, fragment.getGroupedChildrenFragments());
From 1609637ccd0d55f3070f2d68b8f167b2c669d9a1 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Thu, 6 Sep 2018 18:47:40 +0200
Subject: [PATCH 07/16] test source fragments of labeled statement
---
src/test/java/spoon/test/position/TestSourceFragment.java | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/test/java/spoon/test/position/TestSourceFragment.java b/src/test/java/spoon/test/position/TestSourceFragment.java
index 88ed5f534f5..b0a5f316b13 100644
--- a/src/test/java/spoon/test/position/TestSourceFragment.java
+++ b/src/test/java/spoon/test/position/TestSourceFragment.java
@@ -143,7 +143,7 @@ private ElementSourceFragment createFragment(int start, int end) {
}
@Test
- public void testSourceFragmentOfIf() throws Exception {
+ public void testExactSourceFragments() throws Exception {
//contract: SourceFragments of some tricky sources are as expected
final Launcher launcher = new Launcher();
launcher.getEnvironment().setNoClasspath(false);
@@ -171,8 +171,7 @@ public void testSourceFragmentOfIf() throws Exception {
" ", "T", " ", "m3", "(", group("U param", ",", " ", "@Deprecated int p2"), ")", " ", "{\n" +
" return null;\n" +
" }");
- //TODO uncomment and fix source code of element label
- //checkElementFragments(foo.getMethodsByName("m4").get(0).getBody().getStatement(0),"");
+ checkElementFragments(foo.getMethodsByName("m4").get(0).getBody().getStatement(0),"label",":"," ", "while", "(", "true", ")", ";");
checkElementFragments(foo.getMethodsByName("m5").get(0).getBody().getStatement(0),"f", " ", "=", " ", "7.2", ";");
checkElementFragments(((CtAssignment)foo.getMethodsByName("m5").get(0).getBody().getStatement(0)).getAssignment(),"7.2");
From b8b67dafa942b8dd527c6bce494329631db9e363 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Thu, 6 Sep 2018 20:19:41 +0200
Subject: [PATCH 08/16] fix javadoc
---
.../reflect/visitor/printer/change/ElementSourceFragment.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
index 6e2a0ea3efe..b5239625e0c 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
@@ -47,8 +47,7 @@
/**
* Represents a part of source code of an {@link CtElement}
* It is connected into a tree of {@link ElementSourceFragment}s.
- * That tree can be build by {@link CompilationUnit#getOriginalSourceFragment()}
- * And the tree of {@link ElementSourceFragment}s related to one element can be returned by {@link CompilationUnit#getSourceFragment(SourcePositionHolder)}
+ * Use {@link SourcePositionHolder#getOriginalSourceFragment()} to get it.
*/
public class ElementSourceFragment implements SourceFragment {
From 40bfa1bffaa303b608ca9e511858f2a30466baaf Mon Sep 17 00:00:00 2001
From: Martin Monperrus
Date: Thu, 6 Sep 2018 22:27:40 +0200
Subject: [PATCH 09/16] up
---
chore/check-links-in-doc.py | 2 +-
.../spoon/reflect/cu/CompilationUnit.java | 1 +
.../reflect/cu/SourcePositionHolder.java | 2 +-
.../printer/{change => }/SourceFragment.java | 2 +-
.../change/CollectionSourceFragment.java | 86 --
.../printer/change/ElementSourceFragment.java | 878 ------------------
.../printer/change/TokenSourceFragment.java | 49 -
.../visitor/printer/change/TokenType.java | 52 --
.../support/reflect/CtExtendedModifier.java | 2 +-
.../reflect/cu/CompilationUnitImpl.java | 2 +-
.../reflect/declaration/CtElementImpl.java | 2 +-
src/test/java/spoon/test/main/MainTest.java | 3 +-
.../test/position/TestSourceFragment.java | 6 +-
13 files changed, 11 insertions(+), 1076 deletions(-)
rename src/main/java/spoon/reflect/visitor/printer/{change => }/SourceFragment.java (95%)
delete mode 100644 src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java
delete mode 100644 src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
delete mode 100644 src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java
delete mode 100644 src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
diff --git a/chore/check-links-in-doc.py b/chore/check-links-in-doc.py
index 29c6cc855db..b257f5b2418 100644
--- a/chore/check-links-in-doc.py
+++ b/chore/check-links-in-doc.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python2.7
# checks the links of the Spoon documentation
# a problem is reported as an exception, hence as a Unic return code != -1, hence as a build failure
diff --git a/src/main/java/spoon/reflect/cu/CompilationUnit.java b/src/main/java/spoon/reflect/cu/CompilationUnit.java
index c92cea03687..cfd1d14ba35 100644
--- a/src/main/java/spoon/reflect/cu/CompilationUnit.java
+++ b/src/main/java/spoon/reflect/cu/CompilationUnit.java
@@ -164,4 +164,5 @@ enum UNIT_TYPE {
*/
@Experimental
void setImports(Set imports);
+
}
diff --git a/src/main/java/spoon/reflect/cu/SourcePositionHolder.java b/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
index de4a4b367bd..92f76176f33 100644
--- a/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
+++ b/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
@@ -16,7 +16,7 @@
*/
package spoon.reflect.cu;
-import spoon.reflect.visitor.printer.change.ElementSourceFragment;
+import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
/**
* This interface represents an element which knows its position in a source file.
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
similarity index 95%
rename from src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java
rename to src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
index 459f813e0f5..c231dfad8a9 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
@@ -14,7 +14,7 @@
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
-package spoon.reflect.visitor.printer.change;
+package spoon.reflect.visitor.printer;
/**
*/
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java
deleted file mode 100644
index 1a74272be50..00000000000
--- a/src/main/java/spoon/reflect/visitor/printer/change/CollectionSourceFragment.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * Copyright (C) 2006-2018 INRIA and contributors
- * Spoon - http://spoon.gforge.inria.fr/
- *
- * This software is governed by the CeCILL-C License under French law and
- * abiding by the rules of distribution of free software. You can use, modify
- * and/or redistribute the software under the terms of the CeCILL-C license as
- * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
- *
- * This program 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 CeCILL-C License for more details.
- *
- * The fact that you are presently reading this means that you have had
- * knowledge of the CeCILL-C license and that you accept its terms.
- */
-package spoon.reflect.visitor.printer.change;
-
-import java.util.List;
-
-import spoon.reflect.meta.ContainerKind;
-import spoon.reflect.path.CtRole;
-
-/**
- * {@link SourceFragment} of List or Set of {@link ElementSourceFragment}s which belongs to collection role.
- * For example list of Type members or list of parameters, etc.
- * Or set of modifiers and annotations
- */
-public class CollectionSourceFragment implements SourceFragment {
-
- private final List items;
-
- public CollectionSourceFragment(List items) {
- super();
- this.items = items;
- }
-
- @Override
- public String getSourceCode() {
- StringBuilder sb = new StringBuilder();
- for (SourceFragment childSourceFragment : items) {
- sb.append(childSourceFragment.getSourceCode());
- }
- return sb.toString();
- }
-
- /**
- * @return child source fragments of this collection
- */
- public List getItems() {
- return items;
- }
-
- @Override
- public String toString() {
- return items.toString();
- }
-
- /**
- * @return true if collection contains only children of one role handler with container kind LIST
- */
- public boolean isOrdered() {
- CtRole role = null;
- ContainerKind kind = null;
- for (SourceFragment childSourceFragment : items) {
- if (childSourceFragment instanceof ElementSourceFragment) {
- ElementSourceFragment esf = (ElementSourceFragment) childSourceFragment;
- if (role == null) {
- role = esf.getRoleInParent();
- kind = esf.getContainerKindInParent();
- if (kind != ContainerKind.LIST) {
- return false;
- }
- } else {
- if (role != esf.getRoleInParent()) {
- //the collection contains elements of different roles. It cannot be ordered
- return false;
- }
- //else there is another element of the same role - ok
- }
- }
- }
- //there are only elements of one role of container kind LIST
- return true;
- }
-}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
deleted file mode 100644
index b5239625e0c..00000000000
--- a/src/main/java/spoon/reflect/visitor/printer/change/ElementSourceFragment.java
+++ /dev/null
@@ -1,878 +0,0 @@
-/**
- * Copyright (C) 2006-2018 INRIA and contributors
- * Spoon - http://spoon.gforge.inria.fr/
- *
- * This software is governed by the CeCILL-C License under French law and
- * abiding by the rules of distribution of free software. You can use, modify
- * and/or redistribute the software under the terms of the CeCILL-C license as
- * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
- *
- * This program 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 CeCILL-C License for more details.
- *
- * The fact that you are presently reading this means that you have had
- * knowledge of the CeCILL-C license and that you accept its terms.
- */
-package spoon.reflect.visitor.printer.change;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-import spoon.SpoonException;
-import spoon.reflect.code.CtComment;
-import spoon.reflect.code.CtLiteral;
-import spoon.reflect.cu.CompilationUnit;
-import spoon.reflect.cu.SourcePosition;
-import spoon.reflect.cu.SourcePositionHolder;
-import spoon.reflect.declaration.CtElement;
-import spoon.reflect.declaration.CtModifiable;
-import spoon.reflect.meta.ContainerKind;
-import spoon.reflect.meta.RoleHandler;
-import spoon.reflect.meta.impl.RoleHandlerHelper;
-import spoon.reflect.path.CtRole;
-import spoon.reflect.visitor.CtScanner;
-import spoon.support.reflect.CtExtendedModifier;
-import spoon.support.reflect.cu.position.SourcePositionImpl;
-
-/**
- * Represents a part of source code of an {@link CtElement}
- * It is connected into a tree of {@link ElementSourceFragment}s.
- * Use {@link SourcePositionHolder#getOriginalSourceFragment()} to get it.
- */
-public class ElementSourceFragment implements SourceFragment {
-
- /**
- * represents an {@link ElementSourceFragment}, which doesn't exist
- */
- public static final ElementSourceFragment NO_SOURCE_FRAGMENT = new ElementSourceFragment(null, null);
-
- private final SourcePositionHolder element;
- private final RoleHandler roleHandlerInParent;
- private ElementSourceFragment nextSibling;
- private ElementSourceFragment firstChild;
-
- /**
- * Creates a source fragment of {@link SourcePositionHolder}
- *
- * @param element target {@link SourcePositionHolder}
- * @param roleHandlerInParent The {@link RoleHandler}, which defines role of target `element` in it's parent
- */
- public ElementSourceFragment(SourcePositionHolder element, RoleHandler roleHandlerInParent) {
- this.element = element;
- this.roleHandlerInParent = roleHandlerInParent;
- }
-
- /**
- * @return offset of first character which belongs to this fragment
- */
- public int getStart() {
- if (firstChild != null) {
- return Math.min(getSourcePosition().getSourceStart(), firstChild.getStart());
- }
- return getSourcePosition().getSourceStart();
- }
-
- /**
- * @return offset of character after this fragment
- */
- public int getEnd() {
- if (firstChild != null) {
- return Math.max(getSourcePosition().getSourceEnd() + 1, firstChild.getLastSibling().getEnd());
- }
- return getSourcePosition().getSourceEnd() + 1;
- }
-
- /**
- * @return {@link SourcePosition} of this fragment
- */
- public SourcePosition getSourcePosition() {
- return element.getPosition();
- }
-
- @Override
- public String toString() {
- return "|" + getStart() + ", " + getEnd() + "|" + getSourceCode() + "|";
- }
-
- /**
- * @return origin source code of this fragment
- */
- @Override
- public String getSourceCode() {
- return getSourceCode(getStart(), getEnd());
- }
-
- /**
- * @param start start offset relative to compilation unit
- * @param end end offset (after last character) relative to compilation unit
- * @return source code of this Fragment between start/end offsets
- */
- public String getSourceCode(int start, int end) {
- String src = getOriginalSourceCode();
- if (src != null) {
- return src.substring(start, end);
- }
- return null;
- }
-
- /**
- * @return true if position points to same compilation unit (source file) as this SourceFragment
- */
- private boolean isFromSameSource(SourcePosition position) {
- return getSourcePosition().getCompilationUnit().equals(position.getCompilationUnit());
- }
-
- /**
- * Builds tree of {@link SourcePosition}s of `element` and all it's children
- * @param element the root element of the tree
- */
- public void addTreeOfSourceFragmentsOfElement(CtElement element) {
- SourcePosition sp = element.getPosition();
- Deque parents = new ArrayDeque<>();
- parents.push(this);
- //scan all children of `element` and build tree of SourceFragments
- new CtScanner() {
- CtRole scannedRole;
- public void scan(CtRole role, CtElement element) {
- scannedRole = role;
- super.scan(role, element);
- }
- @Override
- protected void enter(CtElement e) {
- ElementSourceFragment newFragment = addChild(parents.peek(), scannedRole, e);
- if (newFragment != null) {
- parents.push(newFragment);
- if (e instanceof CtModifiable) {
- CtModifiable modifiable = (CtModifiable) e;
- Set modifiers = modifiable.getExtendedModifiers();
- for (CtExtendedModifier ctExtendedModifier : modifiers) {
- addChild(newFragment, CtRole.MODIFIER, ctExtendedModifier);
- }
- }
- }
- }
- @Override
- protected void exit(CtElement e) {
- ElementSourceFragment topFragment = parents.peek();
- if (topFragment != null && topFragment.getElement() == e) {
- parents.pop();
- }
- }
- }.scan(element.getRoleInParent(), element);
- }
- /**
- * @param parentFragment the parent {@link ElementSourceFragment}, which will receive {@link ElementSourceFragment} made for `otherElement`
- * @param roleInParent the {@link CtRole} of `otherElement` in scope of element of `parentFragment`
- * @param otherElement {@link SourcePositionHolder} whose {@link ElementSourceFragment} has to be added to `parentFragment`
- * @return new {@link ElementSourceFragment} created for `otherElement` or null if `otherElement` has no source position or doesn't belong to the same compilation unit
- */
- private ElementSourceFragment addChild(ElementSourceFragment parentFragment, CtRole roleInParent, SourcePositionHolder otherElement) {
- SourcePosition otherSourcePosition = otherElement.getPosition();
- if (otherSourcePosition instanceof SourcePositionImpl && otherSourcePosition.getCompilationUnit() != null) {
- SourcePositionImpl childSPI = (SourcePositionImpl) otherSourcePosition;
- if (parentFragment.isFromSameSource(otherSourcePosition)) {
- ElementSourceFragment otherFragment = new ElementSourceFragment(otherElement, parentFragment.getRoleHandler(roleInParent, otherElement));
- //parent and child are from the same file. So we can connect their positions into one tree
- CMP cmp = parentFragment.compare(otherFragment);
- if (cmp == CMP.OTHER_IS_CHILD) {
- //child belongs under parent - OK
- parentFragment.addChild(otherFragment);
- return otherFragment;
- } else {
- if (cmp == CMP.OTHER_IS_AFTER || cmp == CMP.OTHER_IS_BEFORE) {
- if (otherElement instanceof CtComment) {
- /*
- * comments of elements are sometime not included in source position of element.
- * because comments are ignored tokens for java compiler, which computes start/end of elements
- * Example:
- *
- * //a comment
- * aStatement();
- *
- */
- if (otherFragment.getStart() == 0) {
- //it is CompilationUnit comment, which is before package and imports, so it doesn't belong to class
- //No problem. Simply add comment at correct position into SourceFragment tree, starting from ROOT
- addChild(otherFragment);
- return otherFragment;
- }
- //add this child into parent's source fragment and extend that parent source fragment
- parentFragment.addChild(otherFragment);
- return otherFragment;
- }
- }
- //the source position of child element is not included in source position of parent element
- //I (Pavel) am not sure how to handle it, so let's wait until it happens...
-// if (otherElement instanceof CtAnnotation>) {
-// /*
-// * it can happen for annotations of type TYPE_USE and FIELD
-// * In such case the annotation belongs to 2 elements
-// * And one of them cannot have matching source position - OK
-// */
-// return null;
-// }
- //something is wrong ...
- throw new SpoonException("The SourcePosition of elements are not consistent\nparentFragment: " + parentFragment + "\notherFragment: " + otherFragment);
- }
- } else {
- throw new SpoonException("SourcePosition from unexpected compilation unit: " + otherSourcePosition + " expected is: " + parentFragment.getSourcePosition());
- }
- }
- //do not connect that undefined source position
- return null;
- }
-
- private RoleHandler getRoleHandler(CtRole roleInParent, SourcePositionHolder otherElement) {
- SourcePositionHolder parent = element;
- if (parent instanceof CompilationUnit) {
- parent = null;
- }
- if (parent == null) {
- if (otherElement instanceof CtElement) {
- parent = ((CtElement) otherElement).getParent();
- }
- }
- if (parent instanceof CtElement) {
- CtElement ele = (CtElement) parent;
- return RoleHandlerHelper.getRoleHandler(ele.getClass(), roleInParent);
- }
- return null;
- }
-
- /**
- * adds `other` {@link ElementSourceFragment} into tree of {@link ElementSourceFragment}s represented by this root element
- *
- * @param other to be added {@link ElementSourceFragment}
- * @return new root of the tree of the {@link ElementSourceFragment}s. It can be be this or `other`
- */
- public ElementSourceFragment add(ElementSourceFragment other) {
- if (this == other) {
- throw new SpoonException("SourceFragment#add must not be called twice for the same SourceFragment");
- //optionally we might accept that and simply return this
- }
- CMP cmp = this.compare(other);
- switch (cmp) {
- case OTHER_IS_AFTER:
- //other is after this
- addNextSibling(other);
- return this;
- case OTHER_IS_BEFORE:
- //other is before this
- other.addNextSibling(this);
- return other;
- case OTHER_IS_CHILD:
- //other is child of this
- addChild(other);
- return this;
- case OTHER_IS_PARENT:
- //other is parent of this, merge this and all siblings of `this` as children and siblings of `other`
- other.merge(this);
- return other;
- }
- throw new SpoonException("Unexpected compare result: " + cmp);
- }
-
- private void merge(ElementSourceFragment tobeMerged) {
- while (tobeMerged != null) {
- ElementSourceFragment nextTobeMerged = tobeMerged.getNextSibling();
- //disconnect tobeMerged from nextSiblings before we add it. So it is added individually and not with wrong siblings too
- tobeMerged.nextSibling = null;
- add(tobeMerged);
- tobeMerged = nextTobeMerged;
- }
- }
-
- /**
- * adds `fragment` as child fragment of this fragment. If child is located before or after this fragment,
- * then start/end of this fragment is moved
- * @param fragment to be add
- */
- public void addChild(ElementSourceFragment fragment) {
- if (firstChild == null) {
- firstChild = fragment;
- } else {
- firstChild = firstChild.add(fragment);
- }
- }
-
- private void addNextSibling(ElementSourceFragment sibling) {
- if (nextSibling == null) {
- nextSibling = sibling;
- } else {
- nextSibling = nextSibling.add(sibling);
- }
- }
-
- private ElementSourceFragment getLastSibling() {
- ElementSourceFragment lastSibling = this;
- while (lastSibling.nextSibling != null) {
- lastSibling = lastSibling.nextSibling;
- }
- return lastSibling;
- }
-
- private enum CMP {
- OTHER_IS_BEFORE,
- OTHER_IS_AFTER,
- OTHER_IS_CHILD,
- OTHER_IS_PARENT
- }
-
- /**
- * compares this and other
- * @param other other {@link SourcePosition}
- * @return CMP
- * throws {@link SpoonException} if intervals overlap or start/end is negative
- */
- private CMP compare(ElementSourceFragment other) {
- if (other == this) {
- throw new SpoonException("SourcePositionImpl#addNextSibling must not be called twice for the same SourcePosition");
- }
- if (getEnd() <= other.getStart()) {
- //other is after this
- return CMP.OTHER_IS_AFTER;
- }
- if (other.getEnd() <= getStart()) {
- //other is before this
- return CMP.OTHER_IS_BEFORE;
- }
- if (getStart() <= other.getStart() && getEnd() >= other.getEnd()) {
- //other is child of this
- return CMP.OTHER_IS_CHILD;
- }
- if (getStart() >= other.getStart() && getEnd() <= other.getEnd()) {
- //other is parent of this
- return CMP.OTHER_IS_PARENT;
- }
- //the fragments overlap - it is not allowed
- throw new SpoonException("Cannot compare this: [" + getStart() + ", " + getEnd() + "] with other: [\"" + other.getStart() + "\", \"" + other.getEnd() + "\"]");
- }
-
- /**
- * @return {@link ElementSourceFragment} which belongs to the same parent and is next in the sources
- */
- public ElementSourceFragment getNextSibling() {
- return nextSibling;
- }
-
- /**
- * @return {@link ElementSourceFragment}, which is first child of this fragment
- */
- public ElementSourceFragment getFirstChild() {
- return firstChild;
- }
-
- /**
- * Searches the tree of fragments for the {@link ElementSourceFragment} with expected `element`,
- * which contains `start` and `end` source interval.
- * It searches in siblings and children of this {@link ElementSourceFragment} recursively.
- * @param element the {@link SourcePositionHolder} of fragment it is looking for or null for any element
- * @param start the start offset of searched fragment
- * @param end the offset of next character after the end of searched fragment
- *
- * @return {@link ElementSourceFragment} which represents the root of the CtElement whose sources are in interval [start, end]
- */
- public ElementSourceFragment getSourceFragmentOf(SourcePositionHolder element, int start, int end) {
- int myEnd = getEnd();
- if (myEnd <= start) {
- //search in next sibling
- if (nextSibling == null) {
- return null;
- }
- return getRootFragmentOfElement(nextSibling.getSourceFragmentOf(element, start, end));
- }
- int myStart = getStart();
- if (myStart <= start) {
- if (myEnd >= end) {
- if (myStart == start && myEnd == end) {
- //we have found exact match
- if (element != null && getElement() != element) {
- if (firstChild == null) {
- throw new SpoonException("There is no source fragment for element " + element.getClass() + ". There is one for class " + getElement().getClass());
- }
- return firstChild.getSourceFragmentOf(element, start, end);
- }
- return this;
- }
- //it is the child
- if (firstChild == null) {
- if (element != null && getElement() != element) {
- throw new SpoonException("There is no source fragment for element " + element.getClass() + ". There is one for class " + getElement().getClass());
- }
- return this;
- }
- ElementSourceFragment child = getRootFragmentOfElement(firstChild.getSourceFragmentOf(element, start, end));
- if (child != null) {
- //all children are smaller then this element
- return child;
- }
- //so this fragment is last one which wraps whole element
- if (element != null && getElement() != element) {
- throw new SpoonException("There is no source fragment for element " + element.getClass() + ". There is one for class " + getElement().getClass());
- }
- return this;
- }
- //start - end overlaps over multiple fragments
- throw new SpoonException("Invalid start/end interval. It overlaps multiple fragments.");
- }
- return null;
- }
-
- private ElementSourceFragment getRootFragmentOfElement(ElementSourceFragment childFragment) {
- if (childFragment != null && getElement() != null && childFragment.getElement() == getElement()) {
- //child fragment and this fragment have same element. Return this fragment,
- //because we have to return root fragment of CtElement
- return this;
- }
- return childFragment;
- }
- /**
- * @return {@link CtElement} whose source code is contained in this fragment.
- * May be null
- */
- public SourcePositionHolder getElement() {
- return element;
- }
-
- /**
- * Note: the List of children is flat. The child fragments of collections (parameters, type members, ...) are next to each other.
- * @return list of child fragments of this {@link ElementSourceFragment}.
- */
- public List getChildrenFragments() {
- if (element instanceof CtLiteral) {
- return Collections.singletonList(new TokenSourceFragment(getSourceCode(), TokenType.LITERAL));
- }
- List children = new ArrayList<>();
- int off = getStart();
- ElementSourceFragment child = getFirstChild();
- while (child != null) {
- forEachConstantFragment(off, child.getStart(), cf -> children.add(cf));
- children.add(child);
- off = child.getEnd();
- child = child.getNextSibling();
- }
- forEachConstantFragment(off, getEnd(), cf -> children.add(cf));
- return children;
- }
-
- /**
- * Detects all child fragments of this {@link ElementSourceFragment}.
- * Note: the List of children contains one {@link CollectionSourceFragment} for each collection of fragments (parameters, type members, ...).
- * Note: the {@link CollectionSourceFragment} may contain a mix of fragments of different roles, when they overlap.
- * For example this code contains mix of annotations and modifiers
- * public @Deprecated static @Ignored void method()
- * @return list of child fragments of this {@link ElementSourceFragment} where fragments,
- * which belongs to the same collection are grouped into {@link CollectionSourceFragment}
- */
- public List getGroupedChildrenFragments() {
- List flatChildren = getChildrenFragments();
- List result = new ArrayList<>();
- int i = 0;
- while (i < flatChildren.size()) {
- SourceFragment child = flatChildren.get(i);
- if (child instanceof TokenSourceFragment) {
- result.add(child);
- i++;
- continue;
- } else if (child instanceof ElementSourceFragment) {
- ElementSourceFragment esf = (ElementSourceFragment) child;
- ContainerKind kind = esf.getContainerKindInParent();
- if (kind == ContainerKind.SINGLE) {
- //it is root element or there is always only one child instance in parent
- result.add(child);
- i++;
- continue;
- }
- //there can be 0, 1 or more items of children of the same role
- //search for another element of the same role
- Set foundRoles = new HashSet<>();
- foundRoles.add(checkNotNull(esf.getRoleInParent()));
- List childrenInSameCollection = new ArrayList<>();
- //but first include prefix whitespace
- SourceFragment spaceChild = removeSuffixSpace(result);
- if (spaceChild != null) {
- childrenInSameCollection.add(spaceChild);
- }
- childrenInSameCollection.add(esf);
- int lastOfSameRole = findIndexOfLastChildTokenOfRoleHandler(flatChildren, i, esf.getRoleInParent());
- //search for other roles in that interval
- i++;
- while (i <= lastOfSameRole) {
- child = flatChildren.get(i);
- childrenInSameCollection.add(child);
- CtRole role = null;
- if (child instanceof ElementSourceFragment) {
- ElementSourceFragment esf2 = (ElementSourceFragment) child;
- role = esf2.getRoleInParent();
- }
- if (role != null && role != CtRole.COMMENT && foundRoles.add(role)) {
- //there is another role in same block, search for last one
- lastOfSameRole = Math.max(lastOfSameRole, findIndexOfLastChildTokenOfRoleHandler(flatChildren, i + 1, role));
- }
- i++;
- }
- //add suffix space
- if (i < flatChildren.size()) {
- SourceFragment nextChild = flatChildren.get(i);
- if (isSpaceFragment(nextChild)) {
- childrenInSameCollection.add(nextChild);
- i++;
- }
- }
- result.add(new CollectionSourceFragment(childrenInSameCollection));
- } else {
- throw new SpoonException("Unexpected SourceFragment of type " + child.getClass());
- }
- }
- return result;
- }
-
- private SourceFragment removeSuffixSpace(List list) {
- if (list.size() > 0) {
- SourceFragment lastChild = list.get(list.size() - 1);
- if (isSpaceFragment(lastChild)) {
- list.remove(list.size() - 1);
- return lastChild;
- }
- }
- return null;
- }
-
- private T checkNotNull(T o) {
- if (o == null) {
- throw new SpoonException("Unexpected null value");
- }
- return o;
- }
-
- private static int findIndexOfLastChildTokenOfRoleHandler(List childFragments, int start, CtRole role) {
- return findIndexOfPreviousFragment(childFragments, start,
- filter(ElementSourceFragment.class, fragment -> fragment.getRoleInParent() == role));
- }
-
- private enum CharType {
- SPACE,
- NON_SPACE;
-
- static CharType fromChar(char c) {
- return Character.isWhitespace(c) ? SPACE : NON_SPACE;
- }
- }
-
- private static final Set separators = new HashSet<>(Arrays.asList("->", "::", "..."));
- static {
- "(){}[];,.:@=<>?&|".chars().forEach(c -> separators.add(new String(Character.toChars(c))));
- }
- private static final Set operators = new HashSet<>(Arrays.asList(
- "=",
- ">",
- "<",
- "!",
- "~",
- "?",
- ":",
- "==",
- "<=",
- ">=",
- "!=",
- "&&",
- "||",
- "++",
- "--",
- "+",
- "-",
- "*",
- "/",
- "&",
- "|",
- "^",
- "%",
- "<<", ">>", ">>>",
-
- "+=",
- "-=",
- "*=",
- "/=",
- "&=",
- "|=",
- "^=",
- "%=",
- "<<=",
- ">>=",
- ">>>="/*,
- it is handled as keyword here
- "instanceof"
- */
- ));
-
- private static final String[] javaKeywordsJoined = new String[] {
- "abstract continue for new switch",
- "assert default goto package synchronized",
- "boolean do if private this",
- "break double implements protected throw",
- "byte else import public throws",
- "case enum instanceof return transient",
- "catch extends int short try",
- "char final interface static void",
- "class finally long strictfp volatile",
- "const float native super while"};
-
- private static final Set javaKeywords = new HashSet<>();
- static {
- for (String str : javaKeywordsJoined) {
- StringTokenizer st = new StringTokenizer(str, " ");
- while (st.hasMoreTokens()) {
- javaKeywords.add(st.nextToken());
- }
- }
- }
-
- private static final List matchers = new ArrayList<>();
- static {
- separators.forEach(s -> matchers.add(new StringMatcher(s, TokenType.SEPARATOR)));
- operators.forEach(s -> matchers.add(new StringMatcher(s, TokenType.OPERATOR)));
- }
-
- /**
- * Calls `consumer` once for each constant {@link SourceFragment} found in source code between `start` and `end`
- */
- private void forEachConstantFragment(int start, int end, Consumer consumer) {
- if (start == end) {
- return;
- }
- if (start > end) {
- throw new SpoonException("Inconsistent start/end. Start=" + start + " is greater then End=" + end);
- }
- String sourceCode = getOriginalSourceCode();
- StringBuffer buff = new StringBuffer();
- CharType lastType = null;
- int off = start;
- while (off < end) {
- char c = sourceCode.charAt(off);
- CharType type = CharType.fromChar(c);
- if (type != lastType) {
- if (lastType != null) {
- onCharSequence(lastType, buff, consumer);
- buff.setLength(0);
- }
- lastType = type;
- }
- buff.append(c);
- off++;
- }
- onCharSequence(lastType, buff, consumer);
- }
-
- private void onCharSequence(CharType type, StringBuffer buff, Consumer consumer) {
- if (type == CharType.SPACE) {
- consumer.accept(new TokenSourceFragment(buff.toString(), TokenType.SPACE));
- return;
- }
- char[] str = new char[buff.length()];
- buff.getChars(0, buff.length(), str, 0);
- int off = 0;
- while (off < str.length) {
- //detect java identifier or keyword
- int lenOfIdentifier = detectJavaIdentifier(str, off);
- if (lenOfIdentifier > 0) {
- String identifier = new String(str, off, lenOfIdentifier);
- if (javaKeywords.contains(identifier)) {
- //it is a java keyword
- consumer.accept(new TokenSourceFragment(identifier, TokenType.KEYWORD));
- } else {
- //it is a java identifier
- consumer.accept(new TokenSourceFragment(identifier, TokenType.IDENTIFIER));
- }
- off += lenOfIdentifier;
- continue;
- }
- //detect longest match in matchers
- StringMatcher longestMatcher = null;
- for (StringMatcher strMatcher : matchers) {
- if (strMatcher.isMatch(str, off)) {
- longestMatcher = strMatcher.getLonger(longestMatcher);
- }
- }
- if (longestMatcher == null) {
- throw new SpoonException("Unexpected source text: " + buff.toString());
- }
- consumer.accept(new TokenSourceFragment(longestMatcher.toString(), longestMatcher.getType()));
- off += longestMatcher.getLength();
- }
- }
-
- /**
- * @return number of characters in buff starting from start which are java identifier
- */
- private int detectJavaIdentifier(char[] buff, int start) {
- int len = buff.length;
- int o = start;
- if (start <= len) {
- char c = buff[o];
- if (Character.isJavaIdentifierStart(c)) {
- o++;
- while (o < len) {
- c = buff[o];
- if (Character.isJavaIdentifierPart(c) == false) {
- break;
- }
- o++;
- }
- }
- }
- return o - start;
- }
-
- private String getOriginalSourceCode() {
- CompilationUnit cu = getSourcePosition().getCompilationUnit();
- if (cu != null) {
- return cu.getOriginalSourceCode();
- }
- return null;
- }
-
- private static final class StringMatcher {
- private final TokenType type;
- private final char[] chars;
-
- private StringMatcher(final String str, TokenType type) {
- super();
- this.type = type;
- chars = str.toCharArray();
- }
-
- public boolean isMatch(final char[] buffer, int pos) {
- final int len = chars.length;
- if (pos + len > buffer.length) {
- return false;
- }
- for (int i = 0; i < chars.length; i++, pos++) {
- if (chars[i] != buffer[pos]) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public String toString() {
- return new String(chars);
- }
-
- public int getLength() {
- return chars.length;
- }
-
- public StringMatcher getLonger(StringMatcher m) {
- if (m != null && m.getLength() > getLength()) {
- return m;
- }
- return this;
- }
-
- public TokenType getType() {
- return type;
- }
- }
-
- /**
- * @return role of the element of this fragment in scope of it's parent
- */
- public CtRole getRoleInParent() {
- return roleHandlerInParent != null ? roleHandlerInParent.getRole() : null;
- }
-
- /**
- * @return the {@link ContainerKind} of the attribute which holds the element of this fragment in it's parent
- */
- public ContainerKind getContainerKindInParent() {
- if (roleHandlerInParent != null) {
- if (roleHandlerInParent.getRole() != CtRole.COMMENT) {
- return roleHandlerInParent.getContainerKind();
- }
- }
- return ContainerKind.SINGLE;
- }
- /**
- * looks for next fragment whose {@link Predicate} `test` returns true
- * @param start - the index of first to be checked fragment
- * @return index of found fragment, or -1 if not found
- */
- static int findIndexOfNextFragment(List fragments, int start, Predicate test) {
- while (start < fragments.size()) {
- SourceFragment fragment = fragments.get(start);
- if (test.test(fragment)) {
- return start;
- }
- start++;
- }
- return -1;
- }
-
- /**
- * @param start the index of element with lower index which is checked and may be returned
- * @param test a {@link Predicate}, which is evaluated for each item of `fragments` starting from last one and ending with item in index `start`
- * @return index of found fragment, or -1 if not found
- */
- static int findIndexOfPreviousFragment(List fragments, int start, Predicate test) {
- int i = fragments.size() - 1;
- while (i >= start) {
- if (test.test(fragments.get(i))) {
- return i;
- }
- i--;
- }
- return -1;
- }
-
- /**
- * @param predicate the {@link Predicate}, which has to be checkd for each item of {@link CollectionSourceFragment}
- * @return {@link Predicate} which calls `predicate` for each item of {@link CollectionSourceFragment}
- * Returned {@link Predicate} returns true only if `predicate` returns true on at least one item
- */
- static Predicate checkCollectionItems(Predicate predicate) {
- return (SourceFragment fragment) -> {
- if (fragment instanceof CollectionSourceFragment) {
- CollectionSourceFragment collectionFragment = (CollectionSourceFragment) fragment;
- for (SourceFragment itemFragment : collectionFragment.getItems()) {
- if (predicate.test(itemFragment)) {
- return true;
- }
- }
- return false;
- } else {
- return predicate.test(fragment);
- }
- };
- }
-
- /**
- * @param predicate to be called {@link Predicate}
- * @return {@link Predicate} which calls `predicate` only for {@link SourceFragment}s of of type `clazz` and returns false for others
- */
- static Predicate filter(Class clazz, Predicate predicate) {
- return fragment -> {
- if (clazz.isInstance(fragment)) {
- return predicate.test((T) fragment);
- }
- return false;
- };
- }
-
- /**
- * @return true if {@link SourceFragment} represents a white space
- */
- static boolean isSpaceFragment(SourceFragment fragment) {
- return fragment instanceof TokenSourceFragment && ((TokenSourceFragment) fragment).getType() == TokenType.SPACE;
- }
-}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java
deleted file mode 100644
index c394ae5f355..00000000000
--- a/src/main/java/spoon/reflect/visitor/printer/change/TokenSourceFragment.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Copyright (C) 2006-2018 INRIA and contributors
- * Spoon - http://spoon.gforge.inria.fr/
- *
- * This software is governed by the CeCILL-C License under French law and
- * abiding by the rules of distribution of free software. You can use, modify
- * and/or redistribute the software under the terms of the CeCILL-C license as
- * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
- *
- * This program 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 CeCILL-C License for more details.
- *
- * The fact that you are presently reading this means that you have had
- * knowledge of the CeCILL-C license and that you accept its terms.
- */
-package spoon.reflect.visitor.printer.change;
-
-/**
- * a {@link SourceFragment} of some primitive String token.
- */
-public class TokenSourceFragment implements SourceFragment {
-
- private final String source;
- private final TokenType type;
-
- public TokenSourceFragment(String source, TokenType type) {
- super();
- this.source = source;
- this.type = type;
- }
-
- @Override
- public String getSourceCode() {
- return source;
- }
-
- /**
- * @return type of token of this fragment
- */
- public TokenType getType() {
- return type;
- }
-
- @Override
- public String toString() {
- return "|" + getSourceCode() + "|";
- }
-}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java b/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
deleted file mode 100644
index cb05e317a8d..00000000000
--- a/src/main/java/spoon/reflect/visitor/printer/change/TokenType.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Copyright (C) 2006-2018 INRIA and contributors
- * Spoon - http://spoon.gforge.inria.fr/
- *
- * This software is governed by the CeCILL-C License under French law and
- * abiding by the rules of distribution of free software. You can use, modify
- * and/or redistribute the software under the terms of the CeCILL-C license as
- * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
- *
- * This program 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 CeCILL-C License for more details.
- *
- * The fact that you are presently reading this means that you have had
- * knowledge of the CeCILL-C license and that you accept its terms.
- */
-package spoon.reflect.visitor.printer.change;
-
-import spoon.reflect.visitor.TokenWriter;
-
-/**
- * Type of {@link TokenSourceFragment} token.
- * Note: These types mirrors the methods of {@link TokenWriter}
- */
-public enum TokenType {
-
- SEPARATOR(false, false),
- OPERATOR(false, false),
- LITERAL(false, false),
- KEYWORD(false, false),
- IDENTIFIER(false, false),
- CODE_SNIPPET(false, false),
- COMMENT(false, false),
- NEW_LINE(true, false),
- INC_TAB(true, true),
- DEC_TAB(true, true),
- SPACE(true, false);
-
- private final boolean whiteSpace;
- private final boolean tab;
-
- TokenType(boolean whiteSpace, boolean tab) {
- this.whiteSpace = whiteSpace;
- this.tab = tab;
- }
- boolean isWhiteSpace() {
- return whiteSpace;
- }
- public boolean isTab() {
- return tab;
- }
-}
diff --git a/src/main/java/spoon/support/reflect/CtExtendedModifier.java b/src/main/java/spoon/support/reflect/CtExtendedModifier.java
index d8bd7beb2b4..6da4a2f7cef 100644
--- a/src/main/java/spoon/support/reflect/CtExtendedModifier.java
+++ b/src/main/java/spoon/support/reflect/CtExtendedModifier.java
@@ -20,7 +20,7 @@
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.cu.SourcePositionHolder;
import spoon.reflect.declaration.ModifierKind;
-import spoon.reflect.visitor.printer.change.ElementSourceFragment;
+import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
import java.io.Serializable;
diff --git a/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java b/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
index 945e21e6763..aecebf8bf44 100644
--- a/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
+++ b/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java
@@ -27,7 +27,7 @@
import spoon.reflect.factory.Factory;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.filter.TypeFilter;
-import spoon.reflect.visitor.printer.change.ElementSourceFragment;
+import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
import spoon.support.reflect.cu.position.PartialSourcePositionImpl;
import java.io.File;
diff --git a/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java b/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java
index 1b32c409a57..0ec5ed1e78d 100644
--- a/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java
+++ b/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java
@@ -53,7 +53,7 @@
import spoon.reflect.visitor.chain.CtFunction;
import spoon.reflect.visitor.chain.CtQuery;
import spoon.reflect.visitor.filter.AnnotationFilter;
-import spoon.reflect.visitor.printer.change.ElementSourceFragment;
+import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
import spoon.reflect.visitor.CtIterator;
import spoon.support.DefaultCoreFactory;
import spoon.support.DerivedProperty;
diff --git a/src/test/java/spoon/test/main/MainTest.java b/src/test/java/spoon/test/main/MainTest.java
index 75c24c8a543..9201e3f5aa4 100644
--- a/src/test/java/spoon/test/main/MainTest.java
+++ b/src/test/java/spoon/test/main/MainTest.java
@@ -31,7 +31,6 @@
import spoon.reflect.path.CtPathException;
import spoon.reflect.path.CtPathStringBuilder;
import spoon.reflect.path.CtRole;
-import spoon.reflect.reference.CtArrayTypeReference;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtTypeParameterReference;
@@ -40,7 +39,7 @@
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.PrinterHelper;
import spoon.reflect.visitor.filter.TypeFilter;
-import spoon.reflect.visitor.printer.change.ElementSourceFragment;
+import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
import spoon.support.reflect.CtExtendedModifier;
import spoon.test.parent.ParentTest;
diff --git a/src/test/java/spoon/test/position/TestSourceFragment.java b/src/test/java/spoon/test/position/TestSourceFragment.java
index b0a5f316b13..2b9da167928 100644
--- a/src/test/java/spoon/test/position/TestSourceFragment.java
+++ b/src/test/java/spoon/test/position/TestSourceFragment.java
@@ -20,9 +20,9 @@
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
-import spoon.reflect.visitor.printer.change.SourceFragment;
-import spoon.reflect.visitor.printer.change.CollectionSourceFragment;
-import spoon.reflect.visitor.printer.change.ElementSourceFragment;
+import spoon.reflect.visitor.printer.SourceFragment;
+import spoon.reflect.visitor.printer.internal.CollectionSourceFragment;
+import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
import spoon.support.reflect.cu.CompilationUnitImpl;
import spoon.support.reflect.cu.position.SourcePositionImpl;
import spoon.test.position.testclasses.FooSourceFragments;
From 2728369a2b23924e2f5a5edd239e21868424f89a Mon Sep 17 00:00:00 2001
From: Martin Monperrus
Date: Thu, 6 Sep 2018 22:31:09 +0200
Subject: [PATCH 10/16] up
---
src/main/java/spoon/reflect/cu/SourcePositionHolder.java | 3 ++-
src/test/java/spoon/test/main/MainTest.java | 2 +-
src/test/java/spoon/test/position/TestSourceFragment.java | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/main/java/spoon/reflect/cu/SourcePositionHolder.java b/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
index 92f76176f33..6567b74f67a 100644
--- a/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
+++ b/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
@@ -16,6 +16,7 @@
*/
package spoon.reflect.cu;
+import spoon.reflect.visitor.printer.SourceFragment;
import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
/**
@@ -29,7 +30,7 @@ public interface SourcePositionHolder {
* Returns the original source code (maybe different from toString() if a transformation has been applied.
* Or {@link ElementSourceFragment#NO_SOURCE_FRAGMENT} if this element has no original source fragment.
*/
- default ElementSourceFragment getOriginalSourceFragment() {
+ default SourceFragment getOriginalSourceFragment() {
return ElementSourceFragment.NO_SOURCE_FRAGMENT;
}
}
diff --git a/src/test/java/spoon/test/main/MainTest.java b/src/test/java/spoon/test/main/MainTest.java
index 9201e3f5aa4..1927f9999ec 100644
--- a/src/test/java/spoon/test/main/MainTest.java
+++ b/src/test/java/spoon/test/main/MainTest.java
@@ -487,7 +487,7 @@ public void testSourcePositionTreeIsCorrectlyOrdered() {
boolean hasComment = false;
for (CtType type : types) {
SourcePosition sp = type.getPosition();
- totalCount += assertSourcePositionTreeIsCorrectlyOrder(sp.getCompilationUnit().getOriginalSourceFragment(), 0, sp.getCompilationUnit().getOriginalSourceCode().length());
+ totalCount += assertSourcePositionTreeIsCorrectlyOrder((ElementSourceFragment) sp.getCompilationUnit().getOriginalSourceFragment(), 0, sp.getCompilationUnit().getOriginalSourceCode().length());
hasComment = hasComment || type.getComments().size() > 0;
};
assertTrue(totalCount > 1000);
diff --git a/src/test/java/spoon/test/position/TestSourceFragment.java b/src/test/java/spoon/test/position/TestSourceFragment.java
index 2b9da167928..b53ac4251fd 100644
--- a/src/test/java/spoon/test/position/TestSourceFragment.java
+++ b/src/test/java/spoon/test/position/TestSourceFragment.java
@@ -179,7 +179,7 @@ public void testExactSourceFragments() throws Exception {
}
private void checkElementFragments(CtElement ele, Object... expectedFragments) {
- ElementSourceFragment fragment = ele.getOriginalSourceFragment();
+ ElementSourceFragment fragment = (ElementSourceFragment) ele.getOriginalSourceFragment();
List children = fragment.getChildrenFragments();
assertEquals(expandGroup(new ArrayList<>(), expectedFragments), childSourceFragmentsToStrings(children));
assertGroupsEqual(expectedFragments, fragment.getGroupedChildrenFragments());
From 3650f560c4d1bdac18a34921f209a738fc029f74 Mon Sep 17 00:00:00 2001
From: Martin Monperrus
Date: Thu, 6 Sep 2018 22:37:55 +0200
Subject: [PATCH 11/16] up
---
.../visitor/printer/SourceFragment.java | 1 +
.../internal/CollectionSourceFragment.java | 87 ++
.../internal/ElementSourceFragment.java | 879 ++++++++++++++++++
.../printer/internal/TokenSourceFragment.java | 51 +
.../visitor/printer/internal/TokenType.java | 52 ++
5 files changed, 1070 insertions(+)
create mode 100644 src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java
create mode 100644 src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java
create mode 100644 src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java
create mode 100644 src/main/java/spoon/reflect/visitor/printer/internal/TokenType.java
diff --git a/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
index c231dfad8a9..0810b65890b 100644
--- a/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
@@ -17,6 +17,7 @@
package spoon.reflect.visitor.printer;
/**
+ *
*/
public interface SourceFragment {
/**
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java
new file mode 100644
index 00000000000..8437d89cffd
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright (C) 2006-2018 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.reflect.visitor.printer.internal;
+
+import java.util.List;
+
+import spoon.reflect.meta.ContainerKind;
+import spoon.reflect.path.CtRole;
+import spoon.reflect.visitor.printer.SourceFragment;
+
+/**
+ * {@link SourceFragment} of List or Set of {@link ElementSourceFragment}s which belongs to collection role.
+ * For example list of Type members or list of parameters, etc.
+ * Or set of modifiers and annotations
+ */
+public class CollectionSourceFragment implements SourceFragment {
+
+ private final List items;
+
+ public CollectionSourceFragment(List items) {
+ super();
+ this.items = items;
+ }
+
+ @Override
+ public String getSourceCode() {
+ StringBuilder sb = new StringBuilder();
+ for (SourceFragment childSourceFragment : items) {
+ sb.append(childSourceFragment.getSourceCode());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @return child source fragments of this collection
+ */
+ public List getItems() {
+ return items;
+ }
+
+ @Override
+ public String toString() {
+ return items.toString();
+ }
+
+ /**
+ * @return true if collection contains only children of one role handler with container kind LIST
+ */
+ public boolean isOrdered() {
+ CtRole role = null;
+ ContainerKind kind = null;
+ for (SourceFragment childSourceFragment : items) {
+ if (childSourceFragment instanceof ElementSourceFragment) {
+ ElementSourceFragment esf = (ElementSourceFragment) childSourceFragment;
+ if (role == null) {
+ role = esf.getRoleInParent();
+ kind = esf.getContainerKindInParent();
+ if (kind != ContainerKind.LIST) {
+ return false;
+ }
+ } else {
+ if (role != esf.getRoleInParent()) {
+ //the collection contains elements of different roles. It cannot be ordered
+ return false;
+ }
+ //else there is another element of the same role - ok
+ }
+ }
+ }
+ //there are only elements of one role of container kind LIST
+ return true;
+ }
+}
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java
new file mode 100644
index 00000000000..d0ee1049929
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java
@@ -0,0 +1,879 @@
+/**
+ * Copyright (C) 2006-2018 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.reflect.visitor.printer.internal;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import spoon.SpoonException;
+import spoon.reflect.code.CtComment;
+import spoon.reflect.code.CtLiteral;
+import spoon.reflect.cu.CompilationUnit;
+import spoon.reflect.cu.SourcePosition;
+import spoon.reflect.cu.SourcePositionHolder;
+import spoon.reflect.declaration.CtElement;
+import spoon.reflect.declaration.CtModifiable;
+import spoon.reflect.meta.ContainerKind;
+import spoon.reflect.meta.RoleHandler;
+import spoon.reflect.meta.impl.RoleHandlerHelper;
+import spoon.reflect.path.CtRole;
+import spoon.reflect.visitor.CtScanner;
+import spoon.reflect.visitor.printer.SourceFragment;
+import spoon.support.reflect.CtExtendedModifier;
+import spoon.support.reflect.cu.position.SourcePositionImpl;
+
+/**
+ * Represents a part of source code of an {@link CtElement}
+ * It is connected into a tree of {@link ElementSourceFragment}s.
+ * Use {@link SourcePositionHolder#getOriginalSourceFragment()} to get it.
+ */
+public class ElementSourceFragment implements SourceFragment {
+
+ /**
+ * represents an {@link ElementSourceFragment}, which doesn't exist
+ */
+ public static final ElementSourceFragment NO_SOURCE_FRAGMENT = new ElementSourceFragment(null, null);
+
+ private final SourcePositionHolder element;
+ private final RoleHandler roleHandlerInParent;
+ private ElementSourceFragment nextSibling;
+ private ElementSourceFragment firstChild;
+
+ /**
+ * Creates a source fragment of {@link SourcePositionHolder}
+ *
+ * @param element target {@link SourcePositionHolder}
+ * @param roleHandlerInParent The {@link RoleHandler}, which defines role of target `element` in it's parent
+ */
+ public ElementSourceFragment(SourcePositionHolder element, RoleHandler roleHandlerInParent) {
+ this.element = element;
+ this.roleHandlerInParent = roleHandlerInParent;
+ }
+
+ /**
+ * @return offset of first character which belongs to this fragment
+ */
+ public int getStart() {
+ if (firstChild != null) {
+ return Math.min(getSourcePosition().getSourceStart(), firstChild.getStart());
+ }
+ return getSourcePosition().getSourceStart();
+ }
+
+ /**
+ * @return offset of character after this fragment
+ */
+ public int getEnd() {
+ if (firstChild != null) {
+ return Math.max(getSourcePosition().getSourceEnd() + 1, firstChild.getLastSibling().getEnd());
+ }
+ return getSourcePosition().getSourceEnd() + 1;
+ }
+
+ /**
+ * @return {@link SourcePosition} of this fragment
+ */
+ public SourcePosition getSourcePosition() {
+ return element.getPosition();
+ }
+
+ @Override
+ public String toString() {
+ return "|" + getStart() + ", " + getEnd() + "|" + getSourceCode() + "|";
+ }
+
+ /**
+ * @return origin source code of this fragment
+ */
+ @Override
+ public String getSourceCode() {
+ return getSourceCode(getStart(), getEnd());
+ }
+
+ /**
+ * @param start start offset relative to compilation unit
+ * @param end end offset (after last character) relative to compilation unit
+ * @return source code of this Fragment between start/end offsets
+ */
+ public String getSourceCode(int start, int end) {
+ String src = getOriginalSourceCode();
+ if (src != null) {
+ return src.substring(start, end);
+ }
+ return null;
+ }
+
+ /**
+ * @return true if position points to same compilation unit (source file) as this SourceFragment
+ */
+ private boolean isFromSameSource(SourcePosition position) {
+ return getSourcePosition().getCompilationUnit().equals(position.getCompilationUnit());
+ }
+
+ /**
+ * Builds tree of {@link SourcePosition}s of `element` and all it's children
+ * @param element the root element of the tree
+ */
+ public void addTreeOfSourceFragmentsOfElement(CtElement element) {
+ SourcePosition sp = element.getPosition();
+ Deque parents = new ArrayDeque<>();
+ parents.push(this);
+ //scan all children of `element` and build tree of SourceFragments
+ new CtScanner() {
+ CtRole scannedRole;
+ public void scan(CtRole role, CtElement element) {
+ scannedRole = role;
+ super.scan(role, element);
+ }
+ @Override
+ protected void enter(CtElement e) {
+ ElementSourceFragment newFragment = addChild(parents.peek(), scannedRole, e);
+ if (newFragment != null) {
+ parents.push(newFragment);
+ if (e instanceof CtModifiable) {
+ CtModifiable modifiable = (CtModifiable) e;
+ Set modifiers = modifiable.getExtendedModifiers();
+ for (CtExtendedModifier ctExtendedModifier : modifiers) {
+ addChild(newFragment, CtRole.MODIFIER, ctExtendedModifier);
+ }
+ }
+ }
+ }
+ @Override
+ protected void exit(CtElement e) {
+ ElementSourceFragment topFragment = parents.peek();
+ if (topFragment != null && topFragment.getElement() == e) {
+ parents.pop();
+ }
+ }
+ }.scan(element.getRoleInParent(), element);
+ }
+ /**
+ * @param parentFragment the parent {@link ElementSourceFragment}, which will receive {@link ElementSourceFragment} made for `otherElement`
+ * @param roleInParent the {@link CtRole} of `otherElement` in scope of element of `parentFragment`
+ * @param otherElement {@link SourcePositionHolder} whose {@link ElementSourceFragment} has to be added to `parentFragment`
+ * @return new {@link ElementSourceFragment} created for `otherElement` or null if `otherElement` has no source position or doesn't belong to the same compilation unit
+ */
+ private ElementSourceFragment addChild(ElementSourceFragment parentFragment, CtRole roleInParent, SourcePositionHolder otherElement) {
+ SourcePosition otherSourcePosition = otherElement.getPosition();
+ if (otherSourcePosition instanceof SourcePositionImpl && otherSourcePosition.getCompilationUnit() != null) {
+ SourcePositionImpl childSPI = (SourcePositionImpl) otherSourcePosition;
+ if (parentFragment.isFromSameSource(otherSourcePosition)) {
+ ElementSourceFragment otherFragment = new ElementSourceFragment(otherElement, parentFragment.getRoleHandler(roleInParent, otherElement));
+ //parent and child are from the same file. So we can connect their positions into one tree
+ CMP cmp = parentFragment.compare(otherFragment);
+ if (cmp == CMP.OTHER_IS_CHILD) {
+ //child belongs under parent - OK
+ parentFragment.addChild(otherFragment);
+ return otherFragment;
+ } else {
+ if (cmp == CMP.OTHER_IS_AFTER || cmp == CMP.OTHER_IS_BEFORE) {
+ if (otherElement instanceof CtComment) {
+ /*
+ * comments of elements are sometime not included in source position of element.
+ * because comments are ignored tokens for java compiler, which computes start/end of elements
+ * Example:
+ *
+ * //a comment
+ * aStatement();
+ *
+ */
+ if (otherFragment.getStart() == 0) {
+ //it is CompilationUnit comment, which is before package and imports, so it doesn't belong to class
+ //No problem. Simply add comment at correct position into SourceFragment tree, starting from ROOT
+ addChild(otherFragment);
+ return otherFragment;
+ }
+ //add this child into parent's source fragment and extend that parent source fragment
+ parentFragment.addChild(otherFragment);
+ return otherFragment;
+ }
+ }
+ //the source position of child element is not included in source position of parent element
+ //I (Pavel) am not sure how to handle it, so let's wait until it happens...
+// if (otherElement instanceof CtAnnotation>) {
+// /*
+// * it can happen for annotations of type TYPE_USE and FIELD
+// * In such case the annotation belongs to 2 elements
+// * And one of them cannot have matching source position - OK
+// */
+// return null;
+// }
+ //something is wrong ...
+ throw new SpoonException("The SourcePosition of elements are not consistent\nparentFragment: " + parentFragment + "\notherFragment: " + otherFragment);
+ }
+ } else {
+ throw new SpoonException("SourcePosition from unexpected compilation unit: " + otherSourcePosition + " expected is: " + parentFragment.getSourcePosition());
+ }
+ }
+ //do not connect that undefined source position
+ return null;
+ }
+
+ private RoleHandler getRoleHandler(CtRole roleInParent, SourcePositionHolder otherElement) {
+ SourcePositionHolder parent = element;
+ if (parent instanceof CompilationUnit) {
+ parent = null;
+ }
+ if (parent == null) {
+ if (otherElement instanceof CtElement) {
+ parent = ((CtElement) otherElement).getParent();
+ }
+ }
+ if (parent instanceof CtElement) {
+ CtElement ele = (CtElement) parent;
+ return RoleHandlerHelper.getRoleHandler(ele.getClass(), roleInParent);
+ }
+ return null;
+ }
+
+ /**
+ * adds `other` {@link ElementSourceFragment} into tree of {@link ElementSourceFragment}s represented by this root element
+ *
+ * @param other to be added {@link ElementSourceFragment}
+ * @return new root of the tree of the {@link ElementSourceFragment}s. It can be be this or `other`
+ */
+ public ElementSourceFragment add(ElementSourceFragment other) {
+ if (this == other) {
+ throw new SpoonException("SourceFragment#add must not be called twice for the same SourceFragment");
+ //optionally we might accept that and simply return this
+ }
+ CMP cmp = this.compare(other);
+ switch (cmp) {
+ case OTHER_IS_AFTER:
+ //other is after this
+ addNextSibling(other);
+ return this;
+ case OTHER_IS_BEFORE:
+ //other is before this
+ other.addNextSibling(this);
+ return other;
+ case OTHER_IS_CHILD:
+ //other is child of this
+ addChild(other);
+ return this;
+ case OTHER_IS_PARENT:
+ //other is parent of this, merge this and all siblings of `this` as children and siblings of `other`
+ other.merge(this);
+ return other;
+ }
+ throw new SpoonException("Unexpected compare result: " + cmp);
+ }
+
+ private void merge(ElementSourceFragment tobeMerged) {
+ while (tobeMerged != null) {
+ ElementSourceFragment nextTobeMerged = tobeMerged.getNextSibling();
+ //disconnect tobeMerged from nextSiblings before we add it. So it is added individually and not with wrong siblings too
+ tobeMerged.nextSibling = null;
+ add(tobeMerged);
+ tobeMerged = nextTobeMerged;
+ }
+ }
+
+ /**
+ * adds `fragment` as child fragment of this fragment. If child is located before or after this fragment,
+ * then start/end of this fragment is moved
+ * @param fragment to be add
+ */
+ public void addChild(ElementSourceFragment fragment) {
+ if (firstChild == null) {
+ firstChild = fragment;
+ } else {
+ firstChild = firstChild.add(fragment);
+ }
+ }
+
+ private void addNextSibling(ElementSourceFragment sibling) {
+ if (nextSibling == null) {
+ nextSibling = sibling;
+ } else {
+ nextSibling = nextSibling.add(sibling);
+ }
+ }
+
+ private ElementSourceFragment getLastSibling() {
+ ElementSourceFragment lastSibling = this;
+ while (lastSibling.nextSibling != null) {
+ lastSibling = lastSibling.nextSibling;
+ }
+ return lastSibling;
+ }
+
+ private enum CMP {
+ OTHER_IS_BEFORE,
+ OTHER_IS_AFTER,
+ OTHER_IS_CHILD,
+ OTHER_IS_PARENT
+ }
+
+ /**
+ * compares this and other
+ * @param other other {@link SourcePosition}
+ * @return CMP
+ * throws {@link SpoonException} if intervals overlap or start/end is negative
+ */
+ private CMP compare(ElementSourceFragment other) {
+ if (other == this) {
+ throw new SpoonException("SourcePositionImpl#addNextSibling must not be called twice for the same SourcePosition");
+ }
+ if (getEnd() <= other.getStart()) {
+ //other is after this
+ return CMP.OTHER_IS_AFTER;
+ }
+ if (other.getEnd() <= getStart()) {
+ //other is before this
+ return CMP.OTHER_IS_BEFORE;
+ }
+ if (getStart() <= other.getStart() && getEnd() >= other.getEnd()) {
+ //other is child of this
+ return CMP.OTHER_IS_CHILD;
+ }
+ if (getStart() >= other.getStart() && getEnd() <= other.getEnd()) {
+ //other is parent of this
+ return CMP.OTHER_IS_PARENT;
+ }
+ //the fragments overlap - it is not allowed
+ throw new SpoonException("Cannot compare this: [" + getStart() + ", " + getEnd() + "] with other: [\"" + other.getStart() + "\", \"" + other.getEnd() + "\"]");
+ }
+
+ /**
+ * @return {@link ElementSourceFragment} which belongs to the same parent and is next in the sources
+ */
+ public ElementSourceFragment getNextSibling() {
+ return nextSibling;
+ }
+
+ /**
+ * @return {@link ElementSourceFragment}, which is first child of this fragment
+ */
+ public ElementSourceFragment getFirstChild() {
+ return firstChild;
+ }
+
+ /**
+ * Searches the tree of fragments for the {@link ElementSourceFragment} with expected `element`,
+ * which contains `start` and `end` source interval.
+ * It searches in siblings and children of this {@link ElementSourceFragment} recursively.
+ * @param element the {@link SourcePositionHolder} of fragment it is looking for or null for any element
+ * @param start the start offset of searched fragment
+ * @param end the offset of next character after the end of searched fragment
+ *
+ * @return {@link ElementSourceFragment} which represents the root of the CtElement whose sources are in interval [start, end]
+ */
+ public ElementSourceFragment getSourceFragmentOf(SourcePositionHolder element, int start, int end) {
+ int myEnd = getEnd();
+ if (myEnd <= start) {
+ //search in next sibling
+ if (nextSibling == null) {
+ return null;
+ }
+ return getRootFragmentOfElement(nextSibling.getSourceFragmentOf(element, start, end));
+ }
+ int myStart = getStart();
+ if (myStart <= start) {
+ if (myEnd >= end) {
+ if (myStart == start && myEnd == end) {
+ //we have found exact match
+ if (element != null && getElement() != element) {
+ if (firstChild == null) {
+ throw new SpoonException("There is no source fragment for element " + element.getClass() + ". There is one for class " + getElement().getClass());
+ }
+ return firstChild.getSourceFragmentOf(element, start, end);
+ }
+ return this;
+ }
+ //it is the child
+ if (firstChild == null) {
+ if (element != null && getElement() != element) {
+ throw new SpoonException("There is no source fragment for element " + element.getClass() + ". There is one for class " + getElement().getClass());
+ }
+ return this;
+ }
+ ElementSourceFragment child = getRootFragmentOfElement(firstChild.getSourceFragmentOf(element, start, end));
+ if (child != null) {
+ //all children are smaller then this element
+ return child;
+ }
+ //so this fragment is last one which wraps whole element
+ if (element != null && getElement() != element) {
+ throw new SpoonException("There is no source fragment for element " + element.getClass() + ". There is one for class " + getElement().getClass());
+ }
+ return this;
+ }
+ //start - end overlaps over multiple fragments
+ throw new SpoonException("Invalid start/end interval. It overlaps multiple fragments.");
+ }
+ return null;
+ }
+
+ private ElementSourceFragment getRootFragmentOfElement(ElementSourceFragment childFragment) {
+ if (childFragment != null && getElement() != null && childFragment.getElement() == getElement()) {
+ //child fragment and this fragment have same element. Return this fragment,
+ //because we have to return root fragment of CtElement
+ return this;
+ }
+ return childFragment;
+ }
+ /**
+ * @return {@link CtElement} whose source code is contained in this fragment.
+ * May be null
+ */
+ public SourcePositionHolder getElement() {
+ return element;
+ }
+
+ /**
+ * Note: the List of children is flat. The child fragments of collections (parameters, type members, ...) are next to each other.
+ * @return list of child fragments of this {@link ElementSourceFragment}.
+ */
+ public List getChildrenFragments() {
+ if (element instanceof CtLiteral) {
+ return Collections.singletonList(new TokenSourceFragment(getSourceCode(), TokenType.LITERAL));
+ }
+ List children = new ArrayList<>();
+ int off = getStart();
+ ElementSourceFragment child = getFirstChild();
+ while (child != null) {
+ forEachConstantFragment(off, child.getStart(), cf -> children.add(cf));
+ children.add(child);
+ off = child.getEnd();
+ child = child.getNextSibling();
+ }
+ forEachConstantFragment(off, getEnd(), cf -> children.add(cf));
+ return children;
+ }
+
+ /**
+ * Detects all child fragments of this {@link ElementSourceFragment}.
+ * Note: the List of children contains one {@link CollectionSourceFragment} for each collection of fragments (parameters, type members, ...).
+ * Note: the {@link CollectionSourceFragment} may contain a mix of fragments of different roles, when they overlap.
+ * For example this code contains mix of annotations and modifiers
+ * public @Deprecated static @Ignored void method()
+ * @return list of child fragments of this {@link ElementSourceFragment} where fragments,
+ * which belongs to the same collection are grouped into {@link CollectionSourceFragment}
+ */
+ public List getGroupedChildrenFragments() {
+ List flatChildren = getChildrenFragments();
+ List result = new ArrayList<>();
+ int i = 0;
+ while (i < flatChildren.size()) {
+ SourceFragment child = flatChildren.get(i);
+ if (child instanceof TokenSourceFragment) {
+ result.add(child);
+ i++;
+ continue;
+ } else if (child instanceof ElementSourceFragment) {
+ ElementSourceFragment esf = (ElementSourceFragment) child;
+ ContainerKind kind = esf.getContainerKindInParent();
+ if (kind == ContainerKind.SINGLE) {
+ //it is root element or there is always only one child instance in parent
+ result.add(child);
+ i++;
+ continue;
+ }
+ //there can be 0, 1 or more items of children of the same role
+ //search for another element of the same role
+ Set foundRoles = new HashSet<>();
+ foundRoles.add(checkNotNull(esf.getRoleInParent()));
+ List childrenInSameCollection = new ArrayList<>();
+ //but first include prefix whitespace
+ SourceFragment spaceChild = removeSuffixSpace(result);
+ if (spaceChild != null) {
+ childrenInSameCollection.add(spaceChild);
+ }
+ childrenInSameCollection.add(esf);
+ int lastOfSameRole = findIndexOfLastChildTokenOfRoleHandler(flatChildren, i, esf.getRoleInParent());
+ //search for other roles in that interval
+ i++;
+ while (i <= lastOfSameRole) {
+ child = flatChildren.get(i);
+ childrenInSameCollection.add(child);
+ CtRole role = null;
+ if (child instanceof ElementSourceFragment) {
+ ElementSourceFragment esf2 = (ElementSourceFragment) child;
+ role = esf2.getRoleInParent();
+ }
+ if (role != null && role != CtRole.COMMENT && foundRoles.add(role)) {
+ //there is another role in same block, search for last one
+ lastOfSameRole = Math.max(lastOfSameRole, findIndexOfLastChildTokenOfRoleHandler(flatChildren, i + 1, role));
+ }
+ i++;
+ }
+ //add suffix space
+ if (i < flatChildren.size()) {
+ SourceFragment nextChild = flatChildren.get(i);
+ if (isSpaceFragment(nextChild)) {
+ childrenInSameCollection.add(nextChild);
+ i++;
+ }
+ }
+ result.add(new CollectionSourceFragment(childrenInSameCollection));
+ } else {
+ throw new SpoonException("Unexpected SourceFragment of type " + child.getClass());
+ }
+ }
+ return result;
+ }
+
+ private SourceFragment removeSuffixSpace(List list) {
+ if (list.size() > 0) {
+ SourceFragment lastChild = list.get(list.size() - 1);
+ if (isSpaceFragment(lastChild)) {
+ list.remove(list.size() - 1);
+ return lastChild;
+ }
+ }
+ return null;
+ }
+
+ private T checkNotNull(T o) {
+ if (o == null) {
+ throw new SpoonException("Unexpected null value");
+ }
+ return o;
+ }
+
+ private static int findIndexOfLastChildTokenOfRoleHandler(List childFragments, int start, CtRole role) {
+ return findIndexOfPreviousFragment(childFragments, start,
+ filter(ElementSourceFragment.class, fragment -> fragment.getRoleInParent() == role));
+ }
+
+ private enum CharType {
+ SPACE,
+ NON_SPACE;
+
+ static CharType fromChar(char c) {
+ return Character.isWhitespace(c) ? SPACE : NON_SPACE;
+ }
+ }
+
+ private static final Set separators = new HashSet<>(Arrays.asList("->", "::", "..."));
+ static {
+ "(){}[];,.:@=<>?&|".chars().forEach(c -> separators.add(new String(Character.toChars(c))));
+ }
+ private static final Set operators = new HashSet<>(Arrays.asList(
+ "=",
+ ">",
+ "<",
+ "!",
+ "~",
+ "?",
+ ":",
+ "==",
+ "<=",
+ ">=",
+ "!=",
+ "&&",
+ "||",
+ "++",
+ "--",
+ "+",
+ "-",
+ "*",
+ "/",
+ "&",
+ "|",
+ "^",
+ "%",
+ "<<", ">>", ">>>",
+
+ "+=",
+ "-=",
+ "*=",
+ "/=",
+ "&=",
+ "|=",
+ "^=",
+ "%=",
+ "<<=",
+ ">>=",
+ ">>>="/*,
+ it is handled as keyword here
+ "instanceof"
+ */
+ ));
+
+ private static final String[] javaKeywordsJoined = new String[] {
+ "abstract continue for new switch",
+ "assert default goto package synchronized",
+ "boolean do if private this",
+ "break double implements protected throw",
+ "byte else import public throws",
+ "case enum instanceof return transient",
+ "catch extends int short try",
+ "char final interface static void",
+ "class finally long strictfp volatile",
+ "const float native super while"};
+
+ private static final Set javaKeywords = new HashSet<>();
+ static {
+ for (String str : javaKeywordsJoined) {
+ StringTokenizer st = new StringTokenizer(str, " ");
+ while (st.hasMoreTokens()) {
+ javaKeywords.add(st.nextToken());
+ }
+ }
+ }
+
+ private static final List matchers = new ArrayList<>();
+ static {
+ separators.forEach(s -> matchers.add(new StringMatcher(s, TokenType.SEPARATOR)));
+ operators.forEach(s -> matchers.add(new StringMatcher(s, TokenType.OPERATOR)));
+ }
+
+ /**
+ * Calls `consumer` once for each constant {@link SourceFragment} found in source code between `start` and `end`
+ */
+ private void forEachConstantFragment(int start, int end, Consumer consumer) {
+ if (start == end) {
+ return;
+ }
+ if (start > end) {
+ throw new SpoonException("Inconsistent start/end. Start=" + start + " is greater then End=" + end);
+ }
+ String sourceCode = getOriginalSourceCode();
+ StringBuffer buff = new StringBuffer();
+ CharType lastType = null;
+ int off = start;
+ while (off < end) {
+ char c = sourceCode.charAt(off);
+ CharType type = CharType.fromChar(c);
+ if (type != lastType) {
+ if (lastType != null) {
+ onCharSequence(lastType, buff, consumer);
+ buff.setLength(0);
+ }
+ lastType = type;
+ }
+ buff.append(c);
+ off++;
+ }
+ onCharSequence(lastType, buff, consumer);
+ }
+
+ private void onCharSequence(CharType type, StringBuffer buff, Consumer consumer) {
+ if (type == CharType.SPACE) {
+ consumer.accept(new TokenSourceFragment(buff.toString(), TokenType.SPACE));
+ return;
+ }
+ char[] str = new char[buff.length()];
+ buff.getChars(0, buff.length(), str, 0);
+ int off = 0;
+ while (off < str.length) {
+ //detect java identifier or keyword
+ int lenOfIdentifier = detectJavaIdentifier(str, off);
+ if (lenOfIdentifier > 0) {
+ String identifier = new String(str, off, lenOfIdentifier);
+ if (javaKeywords.contains(identifier)) {
+ //it is a java keyword
+ consumer.accept(new TokenSourceFragment(identifier, TokenType.KEYWORD));
+ } else {
+ //it is a java identifier
+ consumer.accept(new TokenSourceFragment(identifier, TokenType.IDENTIFIER));
+ }
+ off += lenOfIdentifier;
+ continue;
+ }
+ //detect longest match in matchers
+ StringMatcher longestMatcher = null;
+ for (StringMatcher strMatcher : matchers) {
+ if (strMatcher.isMatch(str, off)) {
+ longestMatcher = strMatcher.getLonger(longestMatcher);
+ }
+ }
+ if (longestMatcher == null) {
+ throw new SpoonException("Unexpected source text: " + buff.toString());
+ }
+ consumer.accept(new TokenSourceFragment(longestMatcher.toString(), longestMatcher.getType()));
+ off += longestMatcher.getLength();
+ }
+ }
+
+ /**
+ * @return number of characters in buff starting from start which are java identifier
+ */
+ private int detectJavaIdentifier(char[] buff, int start) {
+ int len = buff.length;
+ int o = start;
+ if (start <= len) {
+ char c = buff[o];
+ if (Character.isJavaIdentifierStart(c)) {
+ o++;
+ while (o < len) {
+ c = buff[o];
+ if (Character.isJavaIdentifierPart(c) == false) {
+ break;
+ }
+ o++;
+ }
+ }
+ }
+ return o - start;
+ }
+
+ private String getOriginalSourceCode() {
+ CompilationUnit cu = getSourcePosition().getCompilationUnit();
+ if (cu != null) {
+ return cu.getOriginalSourceCode();
+ }
+ return null;
+ }
+
+ private static final class StringMatcher {
+ private final TokenType type;
+ private final char[] chars;
+
+ private StringMatcher(final String str, TokenType type) {
+ super();
+ this.type = type;
+ chars = str.toCharArray();
+ }
+
+ public boolean isMatch(final char[] buffer, int pos) {
+ final int len = chars.length;
+ if (pos + len > buffer.length) {
+ return false;
+ }
+ for (int i = 0; i < chars.length; i++, pos++) {
+ if (chars[i] != buffer[pos]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return new String(chars);
+ }
+
+ public int getLength() {
+ return chars.length;
+ }
+
+ public StringMatcher getLonger(StringMatcher m) {
+ if (m != null && m.getLength() > getLength()) {
+ return m;
+ }
+ return this;
+ }
+
+ public TokenType getType() {
+ return type;
+ }
+ }
+
+ /**
+ * @return role of the element of this fragment in scope of it's parent
+ */
+ public CtRole getRoleInParent() {
+ return roleHandlerInParent != null ? roleHandlerInParent.getRole() : null;
+ }
+
+ /**
+ * @return the {@link ContainerKind} of the attribute which holds the element of this fragment in it's parent
+ */
+ public ContainerKind getContainerKindInParent() {
+ if (roleHandlerInParent != null) {
+ if (roleHandlerInParent.getRole() != CtRole.COMMENT) {
+ return roleHandlerInParent.getContainerKind();
+ }
+ }
+ return ContainerKind.SINGLE;
+ }
+ /**
+ * looks for next fragment whose {@link Predicate} `test` returns true
+ * @param start - the index of first to be checked fragment
+ * @return index of found fragment, or -1 if not found
+ */
+ static int findIndexOfNextFragment(List fragments, int start, Predicate test) {
+ while (start < fragments.size()) {
+ SourceFragment fragment = fragments.get(start);
+ if (test.test(fragment)) {
+ return start;
+ }
+ start++;
+ }
+ return -1;
+ }
+
+ /**
+ * @param start the index of element with lower index which is checked and may be returned
+ * @param test a {@link Predicate}, which is evaluated for each item of `fragments` starting from last one and ending with item in index `start`
+ * @return index of found fragment, or -1 if not found
+ */
+ static int findIndexOfPreviousFragment(List fragments, int start, Predicate test) {
+ int i = fragments.size() - 1;
+ while (i >= start) {
+ if (test.test(fragments.get(i))) {
+ return i;
+ }
+ i--;
+ }
+ return -1;
+ }
+
+ /**
+ * @param predicate the {@link Predicate}, which has to be checkd for each item of {@link CollectionSourceFragment}
+ * @return {@link Predicate} which calls `predicate` for each item of {@link CollectionSourceFragment}
+ * Returned {@link Predicate} returns true only if `predicate` returns true on at least one item
+ */
+ static Predicate checkCollectionItems(Predicate predicate) {
+ return (SourceFragment fragment) -> {
+ if (fragment instanceof CollectionSourceFragment) {
+ CollectionSourceFragment collectionFragment = (CollectionSourceFragment) fragment;
+ for (SourceFragment itemFragment : collectionFragment.getItems()) {
+ if (predicate.test(itemFragment)) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return predicate.test(fragment);
+ }
+ };
+ }
+
+ /**
+ * @param predicate to be called {@link Predicate}
+ * @return {@link Predicate} which calls `predicate` only for {@link SourceFragment}s of of type `clazz` and returns false for others
+ */
+ static Predicate filter(Class clazz, Predicate predicate) {
+ return fragment -> {
+ if (clazz.isInstance(fragment)) {
+ return predicate.test((T) fragment);
+ }
+ return false;
+ };
+ }
+
+ /**
+ * @return true if {@link SourceFragment} represents a white space
+ */
+ static boolean isSpaceFragment(SourceFragment fragment) {
+ return fragment instanceof TokenSourceFragment && ((TokenSourceFragment) fragment).getType() == TokenType.SPACE;
+ }
+}
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java
new file mode 100644
index 00000000000..e090ae5a74a
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (C) 2006-2018 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.reflect.visitor.printer.internal;
+
+import spoon.reflect.visitor.printer.SourceFragment;
+
+/**
+ * a {@link SourceFragment} of some primitive String token.
+ */
+public class TokenSourceFragment implements SourceFragment {
+
+ private final String source;
+ private final TokenType type;
+
+ public TokenSourceFragment(String source, TokenType type) {
+ super();
+ this.source = source;
+ this.type = type;
+ }
+
+ @Override
+ public String getSourceCode() {
+ return source;
+ }
+
+ /**
+ * @return type of token of this fragment
+ */
+ public TokenType getType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return "|" + getSourceCode() + "|";
+ }
+}
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/TokenType.java b/src/main/java/spoon/reflect/visitor/printer/internal/TokenType.java
new file mode 100644
index 00000000000..a2edf0adf77
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/TokenType.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 2006-2018 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.reflect.visitor.printer.internal;
+
+import spoon.reflect.visitor.TokenWriter;
+
+/**
+ * Type of {@link TokenSourceFragment} token.
+ * Note: These types mirrors the methods of {@link TokenWriter}
+ */
+public enum TokenType {
+
+ SEPARATOR(false, false),
+ OPERATOR(false, false),
+ LITERAL(false, false),
+ KEYWORD(false, false),
+ IDENTIFIER(false, false),
+ CODE_SNIPPET(false, false),
+ COMMENT(false, false),
+ NEW_LINE(true, false),
+ INC_TAB(true, true),
+ DEC_TAB(true, true),
+ SPACE(true, false);
+
+ private final boolean whiteSpace;
+ private final boolean tab;
+
+ TokenType(boolean whiteSpace, boolean tab) {
+ this.whiteSpace = whiteSpace;
+ this.tab = tab;
+ }
+ boolean isWhiteSpace() {
+ return whiteSpace;
+ }
+ public boolean isTab() {
+ return tab;
+ }
+}
From ec98d9f24fad3ac49d6e33701c5f64e06c861883 Mon Sep 17 00:00:00 2001
From: Martin Monperrus
Date: Thu, 6 Sep 2018 22:42:51 +0200
Subject: [PATCH 12/16] up
---
.../spoon/test/architecture/SpoonArchitectureEnforcerTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java
index f557250bea4..5b98bb19b77 100644
--- a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java
+++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java
@@ -365,7 +365,7 @@ public void testSpecPackage() {
officialPackages.add("spoon.reflect.visitor.chain");
officialPackages.add("spoon.reflect.visitor.filter");
officialPackages.add("spoon.reflect.visitor.printer");
- officialPackages.add("spoon.reflect.visitor.printer.change");
+ officialPackages.add("spoon.reflect.visitor.printer.internal");
officialPackages.add("spoon.reflect.visitor");
officialPackages.add("spoon.reflect");
officialPackages.add("spoon.support.comparator");
From 19acd2cad09296446eadd3c821e09ea9eb9e92bf Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Mon, 10 Sep 2018 21:03:05 +0200
Subject: [PATCH 13/16] getOriginalSourceFragment returns
ElementSourceFragment, @experimental
---
src/main/java/spoon/reflect/cu/SourcePositionHolder.java | 3 +--
.../java/spoon/reflect/visitor/printer/SourceFragment.java | 5 ++++-
.../visitor/printer/internal/CollectionSourceFragment.java | 4 +++-
.../visitor/printer/internal/ElementSourceFragment.java | 2 ++
.../visitor/printer/internal/TokenSourceFragment.java | 5 ++++-
src/main/java/spoon/support/reflect/CtExtendedModifier.java | 2 +-
.../spoon/support/reflect/declaration/CtElementImpl.java | 2 +-
src/test/java/spoon/test/main/MainTest.java | 2 +-
src/test/java/spoon/test/position/TestSourceFragment.java | 2 +-
9 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/src/main/java/spoon/reflect/cu/SourcePositionHolder.java b/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
index 6567b74f67a..92f76176f33 100644
--- a/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
+++ b/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
@@ -16,7 +16,6 @@
*/
package spoon.reflect.cu;
-import spoon.reflect.visitor.printer.SourceFragment;
import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
/**
@@ -30,7 +29,7 @@ public interface SourcePositionHolder {
* Returns the original source code (maybe different from toString() if a transformation has been applied.
* Or {@link ElementSourceFragment#NO_SOURCE_FRAGMENT} if this element has no original source fragment.
*/
- default SourceFragment getOriginalSourceFragment() {
+ default ElementSourceFragment getOriginalSourceFragment() {
return ElementSourceFragment.NO_SOURCE_FRAGMENT;
}
}
diff --git a/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
index 0810b65890b..5514f1640cb 100644
--- a/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
@@ -16,9 +16,12 @@
*/
package spoon.reflect.visitor.printer;
+import spoon.support.Experimental;
+
/**
- *
+ * Represents a part of source code
*/
+@Experimental
public interface SourceFragment {
/**
* @return origin source code of whole fragment represented by this instance
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java
index 8437d89cffd..4968e23b904 100644
--- a/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java
@@ -21,12 +21,14 @@
import spoon.reflect.meta.ContainerKind;
import spoon.reflect.path.CtRole;
import spoon.reflect.visitor.printer.SourceFragment;
+import spoon.support.Experimental;
/**
- * {@link SourceFragment} of List or Set of {@link ElementSourceFragment}s which belongs to collection role.
+ * {@link SourceFragment} of List or Set of {@link ElementSourceFragment}s which belong to collection role.
* For example list of Type members or list of parameters, etc.
* Or set of modifiers and annotations
*/
+@Experimental
public class CollectionSourceFragment implements SourceFragment {
private final List items;
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java
index d0ee1049929..00855b76b19 100644
--- a/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java
@@ -42,6 +42,7 @@
import spoon.reflect.path.CtRole;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.printer.SourceFragment;
+import spoon.support.Experimental;
import spoon.support.reflect.CtExtendedModifier;
import spoon.support.reflect.cu.position.SourcePositionImpl;
@@ -50,6 +51,7 @@
* It is connected into a tree of {@link ElementSourceFragment}s.
* Use {@link SourcePositionHolder#getOriginalSourceFragment()} to get it.
*/
+@Experimental
public class ElementSourceFragment implements SourceFragment {
/**
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java
index e090ae5a74a..b68053bc043 100644
--- a/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java
@@ -17,10 +17,13 @@
package spoon.reflect.visitor.printer.internal;
import spoon.reflect.visitor.printer.SourceFragment;
+import spoon.support.Experimental;
/**
- * a {@link SourceFragment} of some primitive String token.
+ * a {@link SourceFragment} of some primitive String token,
+ * like separator, operator, whitespace, ...
*/
+@Experimental
public class TokenSourceFragment implements SourceFragment {
private final String source;
diff --git a/src/main/java/spoon/support/reflect/CtExtendedModifier.java b/src/main/java/spoon/support/reflect/CtExtendedModifier.java
index 6da4a2f7cef..8af784d6622 100644
--- a/src/main/java/spoon/support/reflect/CtExtendedModifier.java
+++ b/src/main/java/spoon/support/reflect/CtExtendedModifier.java
@@ -95,7 +95,7 @@ public ElementSourceFragment getOriginalSourceFragment() {
SourcePosition sp = this.getPosition();
CompilationUnit compilationUnit = sp.getCompilationUnit();
if (compilationUnit != null) {
- ElementSourceFragment rootFragment = (ElementSourceFragment) compilationUnit.getOriginalSourceFragment();
+ ElementSourceFragment rootFragment = compilationUnit.getOriginalSourceFragment();
return rootFragment.getSourceFragmentOf(this, sp.getSourceStart(), sp.getSourceEnd() + 1);
} else {
return ElementSourceFragment.NO_SOURCE_FRAGMENT;
diff --git a/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java b/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java
index 0ec5ed1e78d..8d58c0fb85f 100644
--- a/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java
+++ b/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java
@@ -620,7 +620,7 @@ public ElementSourceFragment getOriginalSourceFragment() {
SourcePosition sp = this.getPosition();
CompilationUnit compilationUnit = sp.getCompilationUnit();
if (compilationUnit != null) {
- ElementSourceFragment rootFragment = (ElementSourceFragment) compilationUnit.getOriginalSourceFragment();
+ ElementSourceFragment rootFragment = compilationUnit.getOriginalSourceFragment();
return rootFragment.getSourceFragmentOf(this, sp.getSourceStart(), sp.getSourceEnd() + 1);
} else {
return ElementSourceFragment.NO_SOURCE_FRAGMENT;
diff --git a/src/test/java/spoon/test/main/MainTest.java b/src/test/java/spoon/test/main/MainTest.java
index 1927f9999ec..9201e3f5aa4 100644
--- a/src/test/java/spoon/test/main/MainTest.java
+++ b/src/test/java/spoon/test/main/MainTest.java
@@ -487,7 +487,7 @@ public void testSourcePositionTreeIsCorrectlyOrdered() {
boolean hasComment = false;
for (CtType type : types) {
SourcePosition sp = type.getPosition();
- totalCount += assertSourcePositionTreeIsCorrectlyOrder((ElementSourceFragment) sp.getCompilationUnit().getOriginalSourceFragment(), 0, sp.getCompilationUnit().getOriginalSourceCode().length());
+ totalCount += assertSourcePositionTreeIsCorrectlyOrder(sp.getCompilationUnit().getOriginalSourceFragment(), 0, sp.getCompilationUnit().getOriginalSourceCode().length());
hasComment = hasComment || type.getComments().size() > 0;
};
assertTrue(totalCount > 1000);
diff --git a/src/test/java/spoon/test/position/TestSourceFragment.java b/src/test/java/spoon/test/position/TestSourceFragment.java
index b53ac4251fd..2b9da167928 100644
--- a/src/test/java/spoon/test/position/TestSourceFragment.java
+++ b/src/test/java/spoon/test/position/TestSourceFragment.java
@@ -179,7 +179,7 @@ public void testExactSourceFragments() throws Exception {
}
private void checkElementFragments(CtElement ele, Object... expectedFragments) {
- ElementSourceFragment fragment = (ElementSourceFragment) ele.getOriginalSourceFragment();
+ ElementSourceFragment fragment = ele.getOriginalSourceFragment();
List children = fragment.getChildrenFragments();
assertEquals(expandGroup(new ArrayList<>(), expectedFragments), childSourceFragmentsToStrings(children));
assertGroupsEqual(expectedFragments, fragment.getGroupedChildrenFragments());
From e6071dfaeb6f3d9c3aca3d803d638f1f93700dba Mon Sep 17 00:00:00 2001
From: Martin Monperrus
Date: Mon, 10 Sep 2018 21:48:46 +0200
Subject: [PATCH 14/16] up
---
chore/check-links-in-doc.py | 2 +-
.../spoon/test/position/TestSourceFragment.java | 13 ++++++++++---
.../testclasses/MethodWithJavaDocAndModifiers.java | 12 ------------
3 files changed, 11 insertions(+), 16 deletions(-)
delete mode 100644 src/test/java/spoon/test/position/testclasses/MethodWithJavaDocAndModifiers.java
diff --git a/chore/check-links-in-doc.py b/chore/check-links-in-doc.py
index b257f5b2418..29c6cc855db 100644
--- a/chore/check-links-in-doc.py
+++ b/chore/check-links-in-doc.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2.7
+#!/usr/bin/python
# checks the links of the Spoon documentation
# a problem is reported as an exception, hence as a Unic return code != -1, hence as a build failure
diff --git a/src/test/java/spoon/test/position/TestSourceFragment.java b/src/test/java/spoon/test/position/TestSourceFragment.java
index 2b9da167928..266c78e5857 100644
--- a/src/test/java/spoon/test/position/TestSourceFragment.java
+++ b/src/test/java/spoon/test/position/TestSourceFragment.java
@@ -1,8 +1,10 @@
package spoon.test.position;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
@@ -154,6 +156,8 @@ public void testExactSourceFragments() throws Exception {
Factory f = comp.getFactory();
final CtType> foo = f.Type().get(FooSourceFragments.class);
+
+ // contract: the fragment returned by getOriginalSourceFragment are correct
checkElementFragments(foo.getMethodsByName("m1").get(0).getBody().getStatement(0),
"if", "(", "x > 0", ")", "{this.getClass();}", "else", "{/*empty*/}");
checkElementFragments(foo.getMethodsByName("m2").get(0).getBody().getStatement(0),
@@ -181,7 +185,10 @@ public void testExactSourceFragments() throws Exception {
private void checkElementFragments(CtElement ele, Object... expectedFragments) {
ElementSourceFragment fragment = ele.getOriginalSourceFragment();
List children = fragment.getChildrenFragments();
- assertEquals(expandGroup(new ArrayList<>(), expectedFragments), childSourceFragmentsToStrings(children));
+
+ // calls getSourceCode on on elements of children
+ assertEquals(expandGroup(new ArrayList<>(), expectedFragments), toCodeStrings(children));
+
assertGroupsEqual(expectedFragments, fragment.getGroupedChildrenFragments());
}
@@ -210,13 +217,13 @@ private static void assertGroupsEqual(Object[] expectedFragments, List {
if (item instanceof CollectionSourceFragment) {
CollectionSourceFragment csf = (CollectionSourceFragment) item;
- return "group("+childSourceFragmentsToStrings(csf.getItems()).toString() + ")";
+ return "group("+ toCodeStrings(csf.getItems()).toString() + ")";
}
return item.getSourceCode();
}).collect(Collectors.toList()));
}
- private static List childSourceFragmentsToStrings(List csf) {
+ private static List toCodeStrings(List csf) {
return csf.stream().map(SourceFragment::getSourceCode).collect(Collectors.toList());
}
}
diff --git a/src/test/java/spoon/test/position/testclasses/MethodWithJavaDocAndModifiers.java b/src/test/java/spoon/test/position/testclasses/MethodWithJavaDocAndModifiers.java
deleted file mode 100644
index 56c9dec7c9f..00000000000
--- a/src/test/java/spoon/test/position/testclasses/MethodWithJavaDocAndModifiers.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package spoon.test.position.testclasses;
-
-public class MethodWithJavaDocAndModifiers {
-
- /**
- * Method with javadoc
- * @param parm1 the parameter
- */
- public @Deprecated int mWithDoc(int parm1) {
- return parm1;
- }
-}
\ No newline at end of file
From 7a8c1f4a896c5dbea972c2ef161970b91d259e4d Mon Sep 17 00:00:00 2001
From: Martin Monperrus
Date: Tue, 11 Sep 2018 15:53:37 +0200
Subject: [PATCH 15/16] up
---
.../spoon/reflect/cu/CompilationUnit.java | 2 +-
.../reflect/cu/SourcePositionHolder.java | 5 ++++
.../visitor/printer/SourceFragment.java | 30 -------------------
.../internal/CollectionSourceFragment.java | 1 -
.../internal/ElementSourceFragment.java | 25 ++++++++--------
.../printer/internal/TokenSourceFragment.java | 1 -
.../test/position/TestSourceFragment.java | 2 +-
7 files changed, 19 insertions(+), 47 deletions(-)
delete mode 100644 src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
diff --git a/src/main/java/spoon/reflect/cu/CompilationUnit.java b/src/main/java/spoon/reflect/cu/CompilationUnit.java
index cfd1d14ba35..0676afcfe54 100644
--- a/src/main/java/spoon/reflect/cu/CompilationUnit.java
+++ b/src/main/java/spoon/reflect/cu/CompilationUnit.java
@@ -17,10 +17,10 @@
package spoon.reflect.cu;
import spoon.processing.FactoryAccessor;
+import spoon.reflect.declaration.CtImport;
import spoon.reflect.declaration.CtModule;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtType;
-import spoon.reflect.declaration.CtImport;
import spoon.support.Experimental;
import java.io.File;
diff --git a/src/main/java/spoon/reflect/cu/SourcePositionHolder.java b/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
index 92f76176f33..7677a182081 100644
--- a/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
+++ b/src/main/java/spoon/reflect/cu/SourcePositionHolder.java
@@ -17,6 +17,7 @@
package spoon.reflect.cu;
import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
+import spoon.support.Experimental;
/**
* This interface represents an element which knows its position in a source file.
@@ -28,7 +29,11 @@ public interface SourcePositionHolder {
/**
* Returns the original source code (maybe different from toString() if a transformation has been applied.
* Or {@link ElementSourceFragment#NO_SOURCE_FRAGMENT} if this element has no original source fragment.
+ *
+ * Warning: this is a advanced method which cannot be considered as part of the stable API
+ *
*/
+ @Experimental
default ElementSourceFragment getOriginalSourceFragment() {
return ElementSourceFragment.NO_SOURCE_FRAGMENT;
}
diff --git a/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
deleted file mode 100644
index 5514f1640cb..00000000000
--- a/src/main/java/spoon/reflect/visitor/printer/SourceFragment.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Copyright (C) 2006-2018 INRIA and contributors
- * Spoon - http://spoon.gforge.inria.fr/
- *
- * This software is governed by the CeCILL-C License under French law and
- * abiding by the rules of distribution of free software. You can use, modify
- * and/or redistribute the software under the terms of the CeCILL-C license as
- * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
- *
- * This program 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 CeCILL-C License for more details.
- *
- * The fact that you are presently reading this means that you have had
- * knowledge of the CeCILL-C license and that you accept its terms.
- */
-package spoon.reflect.visitor.printer;
-
-import spoon.support.Experimental;
-
-/**
- * Represents a part of source code
- */
-@Experimental
-public interface SourceFragment {
- /**
- * @return origin source code of whole fragment represented by this instance
- */
- String getSourceCode();
-}
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java
index 4968e23b904..06f7aa5ca95 100644
--- a/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/CollectionSourceFragment.java
@@ -20,7 +20,6 @@
import spoon.reflect.meta.ContainerKind;
import spoon.reflect.path.CtRole;
-import spoon.reflect.visitor.printer.SourceFragment;
import spoon.support.Experimental;
/**
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java
index 00855b76b19..ac4be63ff1c 100644
--- a/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/ElementSourceFragment.java
@@ -16,18 +16,6 @@
*/
package spoon.reflect.visitor.printer.internal;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
import spoon.SpoonException;
import spoon.reflect.code.CtComment;
import spoon.reflect.code.CtLiteral;
@@ -41,11 +29,22 @@
import spoon.reflect.meta.impl.RoleHandlerHelper;
import spoon.reflect.path.CtRole;
import spoon.reflect.visitor.CtScanner;
-import spoon.reflect.visitor.printer.SourceFragment;
import spoon.support.Experimental;
import spoon.support.reflect.CtExtendedModifier;
import spoon.support.reflect.cu.position.SourcePositionImpl;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
/**
* Represents a part of source code of an {@link CtElement}
* It is connected into a tree of {@link ElementSourceFragment}s.
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java
index b68053bc043..dbdea67eb9a 100644
--- a/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/TokenSourceFragment.java
@@ -16,7 +16,6 @@
*/
package spoon.reflect.visitor.printer.internal;
-import spoon.reflect.visitor.printer.SourceFragment;
import spoon.support.Experimental;
/**
diff --git a/src/test/java/spoon/test/position/TestSourceFragment.java b/src/test/java/spoon/test/position/TestSourceFragment.java
index 266c78e5857..b5227987026 100644
--- a/src/test/java/spoon/test/position/TestSourceFragment.java
+++ b/src/test/java/spoon/test/position/TestSourceFragment.java
@@ -22,7 +22,7 @@
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
-import spoon.reflect.visitor.printer.SourceFragment;
+import spoon.reflect.visitor.printer.internal.SourceFragment;
import spoon.reflect.visitor.printer.internal.CollectionSourceFragment;
import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
import spoon.support.reflect.cu.CompilationUnitImpl;
From a9323985c44afc67d37cfd7e303f2c8974436965 Mon Sep 17 00:00:00 2001
From: Martin Monperrus
Date: Tue, 11 Sep 2018 16:14:25 +0200
Subject: [PATCH 16/16] up
---
.../printer/internal/SourceFragment.java | 32 +++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 src/main/java/spoon/reflect/visitor/printer/internal/SourceFragment.java
diff --git a/src/main/java/spoon/reflect/visitor/printer/internal/SourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/internal/SourceFragment.java
new file mode 100644
index 00000000000..5046882cc36
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/internal/SourceFragment.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (C) 2006-2018 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.reflect.visitor.printer.internal;
+
+import spoon.support.Experimental;
+
+/**
+ * Represents a part of source code.
+ *
+ * See https://github.com/INRIA/spoon/pull/2283
+ */
+@Experimental
+public interface SourceFragment {
+ /**
+ * @return origin source code of whole fragment represented by this instance
+ */
+ String getSourceCode();
+}