1/*
2 * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
3 * Copyright (C) 2011, 2013-2016 The JavaParser Team.
4 *
5 * This file is part of JavaParser.
6 *
7 * JavaParser can be used either under the terms of
8 * a) the GNU Lesser General Public License as published by
9 *     the Free Software Foundation, either version 3 of the License, or
10 *     (at your option) any later version.
11 * b) the terms of the Apache License
12 *
13 * You should have received a copy of both licenses in LICENCE.LGPL and
14 * LICENCE.APACHE. Please refer to those files for details.
15 *
16 * JavaParser is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 * GNU Lesser General Public License for more details.
20 */
21
22package com.github.javaparser.ast;
23
24import com.github.javaparser.Position;
25import com.github.javaparser.Range;
26import com.github.javaparser.ast.comments.BlockComment;
27import com.github.javaparser.ast.comments.Comment;
28import com.github.javaparser.ast.comments.LineComment;
29import com.github.javaparser.ast.visitor.*;
30
31import java.util.*;
32
33/**
34 * Abstract class for all nodes of the AST.
35 *
36 * Each Node can have one associated comment which describe it and
37 * a number of "orphan comments" which it contains but are not specifically
38 * associated to any element.
39 *
40 * @author Julio Vilmar Gesser
41 */
42public abstract class Node implements Cloneable {
43    /**
44     * This can be used to sort nodes on position.
45     */
46    public static Comparator<Node> NODE_BY_BEGIN_POSITION = (a, b) -> a.getBegin().compareTo(b.getBegin());
47
48    private Range range;
49
50    private Node parentNode;
51
52    private List<Node> childrenNodes = new LinkedList<>();
53    private List<Comment> orphanComments = new LinkedList<>();
54
55    private IdentityHashMap<UserDataKey<?>, Object> userData = null;
56
57    private Comment comment;
58
59    public Node() {
60        this(Range.UNKNOWN);
61    }
62
63    public Node(Range range) {
64        this.range = range;
65    }
66
67    /**
68     * Accept method for visitor support.
69     *
70     * @param <R>
71     *            the type the return value of the visitor
72     * @param <A>
73     *            the type the argument passed to the visitor
74     * @param v
75     *            the visitor implementation
76     * @param arg
77     *            the argument passed to the visitor
78     * @return the result of the visit
79     */
80    public abstract <R, A> R accept(GenericVisitor<R, A> v, A arg);
81
82    /**
83     * Accept method for visitor support.
84     *
85     * @param <A>
86     *            the type the argument passed for the visitor
87     * @param v
88     *            the visitor implementation
89     * @param arg
90     *            any value relevant for the visitor
91     */
92    public abstract <A> void accept(VoidVisitor<A> v, A arg);
93
94    /**
95     * This is a comment associated with this node.
96     *
97     * @return comment property
98     */
99    public final Comment getComment() {
100        return comment;
101    }
102
103    /**
104     * The begin position of this node in the source file.
105     */
106    public Position getBegin() {
107        return range.begin;
108    }
109
110    /**
111     * The end position of this node in the source file.
112     */
113    public Position getEnd() {
114        return range.end;
115    }
116
117    /**
118     * Sets the begin position of this node in the source file.
119     */
120    public Node setBegin(Position begin) {
121        range = range.withBegin(begin);
122        return this;
123    }
124
125    /**
126     * Sets the end position of this node in the source file.
127     */
128    public Node setEnd(Position end) {
129        range = range.withEnd(end);
130        return this;
131    }
132
133    /**
134     * @return the range of characters in the source code that this node covers.
135     */
136    public Range getRange() {
137        return range;
138    }
139
140    /**
141     * @param range the range of characters in the source code that this node covers.
142     */
143    public Node setRange(Range range) {
144        this.range = range;
145        return this;
146    }
147
148    /**
149     * Use this to store additional information to this node.
150     *
151     * @param comment to be set
152     */
153    public final Node setComment(final Comment comment) {
154        if (comment != null && (this instanceof Comment)) {
155            throw new RuntimeException("A comment can not be commented");
156        }
157        if (this.comment != null) {
158            this.comment.setCommentedNode(null);
159        }
160        this.comment = comment;
161        if (comment != null) {
162            this.comment.setCommentedNode(this);
163        }
164        return this;
165    }
166
167
168
169    /**
170     * Use this to store additional information to this node.
171     *
172     * @param comment to be set
173     */
174    public final Node setLineComment(String comment) {
175        return setComment(new LineComment(comment));
176    }
177
178    /**
179     * Use this to store additional information to this node.
180     *
181     * @param comment to be set
182     */
183    public final Node setBlockComment(String comment) {
184        return setComment(new BlockComment(comment));
185    }
186
187    /**
188     * Return the String representation of this node.
189     *
190     * @return the String representation of this node
191     */
192    @Override
193    public final String toString() {
194        final DumpVisitor visitor = new DumpVisitor();
195        accept(visitor, null);
196        return visitor.getSource();
197    }
198
199    public final String toStringWithoutComments() {
200        final DumpVisitor visitor = new DumpVisitor(false);
201        accept(visitor, null);
202        return visitor.getSource();
203    }
204
205    @Override
206    public final int hashCode() {
207        return toString().hashCode();
208    }
209
210    @Override
211    public boolean equals(final Object obj) {
212        if (obj == null || !(obj instanceof Node)) {
213            return false;
214        }
215        return EqualsVisitor.equals(this, (Node) obj);
216    }
217
218    @Override
219    public Node clone() {
220        return this.accept(new CloneVisitor(), null);
221    }
222
223    public Node getParentNode() {
224        return parentNode;
225    }
226
227    @SuppressWarnings("unchecked")
228    public <T> T getParentNodeOfType(Class<T> classType) {
229        Node parent = parentNode;
230        while (parent != null) {
231            if (classType.isAssignableFrom(parent.getClass()))
232                return (T) parent;
233            parent = parent.parentNode;
234        }
235        return null;
236    }
237
238    public List<Node> getChildrenNodes() {
239        return childrenNodes;
240    }
241
242    public boolean contains(Node other) {
243        return range.contains(other.range);
244    }
245
246    public void addOrphanComment(Comment comment) {
247        orphanComments.add(comment);
248        comment.setParentNode(this);
249    }
250
251    /**
252     * This is a list of Comment which are inside the node and are not associated
253     * with any meaningful AST Node.
254     *
255     * For example, comments at the end of methods (immediately before the parenthesis)
256     * or at the end of CompilationUnit are orphan comments.
257     *
258     * When more than one comment preceeds a statement, the one immediately preceding it
259     * it is associated with the statements, while the others are orphans.
260     *
261     * @return all comments that cannot be attributed to a concept
262     */
263    public List<Comment> getOrphanComments() {
264        return orphanComments;
265    }
266
267    /**
268     * This is the list of Comment which are contained in the Node either because
269     * they are properly associated to one of its children or because they are floating
270     * around inside the Node
271     *
272     * @return all Comments within the node as a list
273     */
274    public List<Comment> getAllContainedComments() {
275        List<Comment> comments = new LinkedList<>();
276        comments.addAll(getOrphanComments());
277
278        for (Node child : getChildrenNodes()) {
279            if (child.getComment() != null) {
280                comments.add(child.getComment());
281            }
282            comments.addAll(child.getAllContainedComments());
283        }
284
285        return comments;
286    }
287
288    /**
289     * Assign a new parent to this node, removing it
290     * from the list of children of the previous parent, if any.
291     *
292     * @param parentNode node to be set as parent
293     */
294    public void setParentNode(Node parentNode) {
295        // remove from old parent, if any
296        if (this.parentNode != null) {
297            this.parentNode.childrenNodes.remove(this);
298        }
299        this.parentNode = parentNode;
300        // add to new parent, if any
301        if (this.parentNode != null) {
302            this.parentNode.childrenNodes.add(this);
303        }
304    }
305
306    protected void setAsParentNodeOf(List<? extends Node> childNodes) {
307        if (childNodes != null) {
308            for (Node current : childNodes) {
309                current.setParentNode(this);
310            }
311        }
312    }
313
314    protected void setAsParentNodeOf(Node childNode) {
315        if (childNode != null) {
316            childNode.setParentNode(this);
317        }
318    }
319
320    public static final int ABSOLUTE_BEGIN_LINE = -1;
321    public static final int ABSOLUTE_END_LINE = -2;
322
323    public boolean isPositionedAfter(Position position) {
324        return range.isAfter(position);
325    }
326
327    public boolean isPositionedBefore(Position position) {
328        return range.isBefore(position);
329    }
330
331    public boolean hasComment() {
332        return comment != null;
333    }
334
335    public void tryAddImportToParentCompilationUnit(Class<?> clazz) {
336        CompilationUnit parentNode = getParentNodeOfType(CompilationUnit.class);
337        if (parentNode != null) {
338            parentNode.addImport(clazz);
339        }
340    }
341
342    /**
343     * Recursively finds all nodes of a certain type.
344     *
345     * @param clazz the type of node to find.
346     */
347    public <N extends Node> List<N> getNodesByType(Class<N> clazz) {
348        List<N> nodes = new ArrayList<>();
349        for (Node child : getChildrenNodes()) {
350            if (clazz.isInstance(child)) {
351                nodes.add(clazz.cast(child));
352            }
353            nodes.addAll(child.getNodesByType(clazz));
354        }
355        return nodes;
356    }
357
358    /**
359     * Gets user data for this component using the given key.
360     *
361     * @param <M>
362     *            The type of the user data.
363     *
364     * @param key
365     *            The key for the data
366     * @return The user data or null of no user data was found for the given key
367     * @see UserDataKey
368     */
369    public <M> M getUserData(final UserDataKey<M> key) {
370        if (userData == null) {
371            return null;
372        }
373        return (M) userData.get(key);
374    }
375
376    /**
377     * Sets user data for this component using the given key.
378     * For information on creating UserDataKey, see {@link UserDataKey}.
379     *
380     * @param <M>
381     *            The type of user data
382     *
383     * @param key
384     *            The singleton key for the user data
385     * @param object
386     *            The user data object
387     * @throws IllegalArgumentException
388     * @see UserDataKey
389     */
390    public <M> void setUserData(UserDataKey<M> key, M object) {
391        if (userData == null) {
392            userData = new IdentityHashMap<>();
393        }
394        userData.put(key, object);
395    }
396}
397