blob: 9144c92c35078236262971b1c75cd2379e5ef781 [file] [log] [blame]
/*
*
* 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.royale.compiler.common;
import org.apache.royale.compiler.config.CompilerDiagnosticsConstants;
import org.apache.royale.compiler.filespecs.IFileSpecification;
import org.apache.royale.compiler.internal.scopes.ASFileScope;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.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) {
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.NODE_REFERENCES) == CompilerDiagnosticsConstants.NODE_REFERENCES)
System.out.println("NodeReference getting lock for " + fileSpec.getPath());
// 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);
}
}
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.NODE_REFERENCES) == CompilerDiagnosticsConstants.NODE_REFERENCES)
System.out.println("NodeReference done with lock for " + fileSpec.getPath());
}
}
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;
}
}