/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.common; import org.apache.flex.compiler.filespecs.IFileSpecification; import org.apache.flex.compiler.internal.scopes.ASFileScope; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.workspaces.IWorkspace; import java.lang.ref.WeakReference; /** * Helper class to abstract the details of how we get back to Nodes from Definitions and NamespaceReferences. * * This class holds a WeakRef to the IASNode, and is able to regenerate the IASNode if needed after it has been collected. */ public class NodeReference { /** * Construct a NodeReference to the given node * @param node the node to references */ public NodeReference(IASNode node) { absoluteStart = node.getAbsoluteStart(); nodeRef = new WeakReference<IASNode>(node); fileSpec = node.getFileSpecification(); } /** * Construct a NodeReference to a node in the specified file at the * specified offset. This constructor is used from the MXMLScopeBuilder, * where we only have temporary tree nodes. * * @param containingFileSpec File that contains the node this reference * refers to. * @param absoluteStart The offset at which the node this reference refers to * starts. */ public NodeReference(IFileSpecification containingFileSpec, int absoluteStart) { this.absoluteStart = absoluteStart; nodeRef = new WeakReference<IASNode>(null); fileSpec = containingFileSpec; } // The node that produced this definition, if this definition came from source. // This is stored as a weak reference so that symbol tables don't prevent ASTs // from being garbage-collected. // If the reference is lost, getNode() restores it by using // 'fileSpec' to reparse the AST and 'start' to find the correct node in that AST. private WeakReference<IASNode> nodeRef; // The {@link IFileSpecification} from which this definition came. private IFileSpecification fileSpec; // The absolute starting offset of this definition. private int absoluteStart = ISourceLocation.UNKNOWN; /** * Get the file spec for this Node */ public IFileSpecification getFileSpecification() { return fileSpec; } /** * Get the start offset for the node */ public int getAbsoluteStart() { return absoluteStart; } /** * Finds the node closest to the root whose start offset is exactly the * specified offset. * <p> * This method differs from * {@link IASNode#getContainingNode(int)} * in that it considers the start of a node to be contained by the node. * * @param root Node to start the search at. * @param absoluteOffset Start offset of the node to find. * @return The node closest to the specified root node whose start offset is * the specified offset, or null if no such node exists. */ private static IASNode findNode(IASNode root, int absoluteOffset) { IASNode current = root; assert root.getAbsoluteEnd() >= absoluteOffset; // These two nested while loops rely on the fact that // the start and end offset of a node enclose the start // offset of the nodes first child and the end offset of the // nodes last child. while ((current != null) && (current.getAbsoluteStart() != absoluteOffset)) { assert current.getAbsoluteStart() < absoluteOffset; assert current.getAbsoluteEnd() >= absoluteOffset; int childCount = current.getChildCount(); int i = 0; IASNode next = null; // This inner loop computes which child node to descend to. // We want to descend to the child contains the // start offset. The child nodes are always in file order, // and their source ranges do not overlap. while ((i < childCount) && (next == null)) { IASNode currChild = current.getChild(i); // Once we find a child that ends after the offset // we are looking for, descend to that child. if (currChild != null && currChild.getAbsoluteEnd() >= absoluteOffset) next = currChild; ++i; } current = next; } return current; } /** * Get a strong reference to the Node. If necessary this will reparse the file to find the correct Node. * This method may return null, such as when document is so malformed * that we can't produce a file node for it. * (Note that MXMLScopeBuilder can produce definitions with NodeReferences into a malformed document.) * @param workspace the Workspace to use to parse the file * @param scope the containing scope of the definition we want the node for - this is used * to find the ASFileScope it's contained in to reconnect the definitions and Nodes * if the file needs to be reparsed */ public IASNode getNode(IWorkspace workspace, ASScope scope) { // If this definition didn't come from source, return null. if (fileSpec == null || absoluteStart == ISourceLocation.UNKNOWN) return null; // If the definition node is still weakly attached, return it. IASNode node = nodeRef.get(); if (node != null) return node; // Reparse the file from which this definition came // to create a new AST. // TODO Detect whether the file has changed since the definition was created. if( workspace != null ) { ASScope s = scope; ASFileScope fileScope = null; // Get the file scope, and reconnect the AST to the definitions/scopes that // already exist while( s != null ) { if( s instanceof ASFileScope ) { fileScope = (ASFileScope)s; } s = s.getContainingScope(); } if( fileScope != null) { // Grab the lock synchronized (this) { // Check again in case another thread already updated the node reference node = nodeRef.get(); if(node != null ) return node; // Get the file node from the file scope IASNode fileNode = fileScope.getNode(); if (fileNode != null) { // Find the node that produced this definition, // using the starting offset stored in this definition. node = findNode(fileNode, absoluteStart); // Reattach a weak reference to the node. nodeRef = new WeakReference<IASNode>(node); } } } } return node; } /** * Get the node from the underlying weak reference. This will not do any parsing * or any other work to recover the Node if it has been collected * @return the IASNode this reference refers to, or null if the Node no longer exists */ public IASNode getNodeIfExists() { return nodeRef != null ? nodeRef.get() : null; } /** * Explicitly re-connect this node reference to the specified node. * @param node The {@link IASNode} to re-connect this node reference to. */ public void reconnectNode(IASNode node) { assert node.contains(absoluteStart); nodeRef = new WeakReference<IASNode>(node); } /** * static that represents no NodeReference (e.g. for defs that came from an ABC) */ public static NodeReference noReference = new NodeReference(); /** * Private ctor only used by the noReference instance. */ private NodeReference() { this.nodeRef = null; this.fileSpec = null; this.absoluteStart = ISourceLocation.UNKNOWN; } }