/******************************************************************************* * Copyright (c) 2015, 2016 Google, Inc and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Stefan Xenos (Google) - Initial implementation *******************************************************************************/ package org.eclipse.jdt.internal.core.nd.java; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.internal.core.nd.Nd; import org.eclipse.jdt.internal.core.nd.db.Database; import org.eclipse.jdt.internal.core.nd.db.IString; import org.eclipse.jdt.internal.core.nd.db.IndexException; import org.eclipse.jdt.internal.core.nd.field.FieldLong; import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany; import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany.Visitor; import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex.IResultRank; import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex.SearchCriteria; import org.eclipse.jdt.internal.core.nd.field.FieldSearchKey; import org.eclipse.jdt.internal.core.nd.field.FieldString; import org.eclipse.jdt.internal.core.nd.field.StructDef; /** * Represents a source of java classes (such as a .jar or .class file). */ public class NdResourceFile extends NdTreeNode { public static final FieldSearchKey<JavaIndex> FILENAME; public static final FieldOneToMany<NdBinding> ALL_NODES; public static final FieldLong TIME_LAST_USED; public static final FieldLong TIME_LAST_SCANNED; public static final FieldLong SIZE_LAST_SCANNED; public static final FieldLong HASHCODE_LAST_SCANNED; public static final FieldOneToMany<NdWorkspaceLocation> WORKSPACE_MAPPINGS; public static final FieldString JAVA_ROOT; public static final FieldLong JDK_LEVEL; @SuppressWarnings("hiding") public static final StructDef<NdResourceFile> type; static { type = StructDef.create(NdResourceFile.class, NdTreeNode.type); FILENAME = FieldSearchKey.create(type, JavaIndex.FILES); ALL_NODES = FieldOneToMany.create(type, NdBinding.FILE, 16); TIME_LAST_USED = type.addLong(); TIME_LAST_SCANNED = type.addLong(); SIZE_LAST_SCANNED = type.addLong(); HASHCODE_LAST_SCANNED = type.addLong(); WORKSPACE_MAPPINGS = FieldOneToMany.create(type, NdWorkspaceLocation.RESOURCE); JAVA_ROOT = type.addString(); JDK_LEVEL = type.addLong(); type.done(); } private long jdkLevel; public NdResourceFile(Nd dom, long address) { super(dom, address); } public NdResourceFile(Nd nd) { super(nd, null); } public long getJdkLevel() { if (this.jdkLevel == 0) { this.jdkLevel = JDK_LEVEL.get(getNd(), this.address); } return this.jdkLevel; } public void setJdkLevel(long jdkLevel) { if (getJdkLevel() != jdkLevel) { JDK_LEVEL.put(getNd(), this.address, jdkLevel); } } public List<NdTreeNode> getChildren() { return CHILDREN.asList(this.getNd(), this.address); } /** * Determines whether this file is still in the index. If a {@link NdResourceFile} instance is retained while the * database lock is released and reobtained, this method should be invoked to ensure that the {@link NdResourceFile} * has not been deleted in the meantime. */ public boolean isInIndex() { try { Nd nd = getNd(); // In the common case where the resource file was deleted and the memory hasn't yet been reused, // this will fail. if (!nd.isValidAddress(this.address) || NODE_TYPE.get(nd, this.address) != nd.getNodeType(getClass())) { return false; } char[] filename = FILENAME.get(getNd(), this.address).getChars(); NdResourceFile result = JavaIndex.FILES.findBest(nd, Database.DATA_AREA_OFFSET, SearchCriteria.create(filename), new IResultRank() { @Override public long getRank(Nd testNd, long testAddress) { if (testAddress == NdResourceFile.this.address) { return 1; } return -1; } }); return (this.equals(result)); } catch (IndexException e) { // Read errors are expected here. It's possible that the resource file has been deleted and something // new was written to this address, in which case we may be reading random gibberish from the database. // This is likely to cause an exception. return false; } } public List<IPath> getAllWorkspaceLocations() { final List<IPath> result = new ArrayList<>(); WORKSPACE_MAPPINGS.accept(getNd(), this.address, new Visitor<NdWorkspaceLocation>() { @Override public void visit(int index, NdWorkspaceLocation toVisit) { result.add(new Path(toVisit.getPath().getString())); } }); return result; } public IPath getFirstWorkspaceLocation() { if (WORKSPACE_MAPPINGS.isEmpty(getNd(), this.address)) { return Path.EMPTY; } return new Path(WORKSPACE_MAPPINGS.get(getNd(), this.address, 0).getPath().toString()); } public IPath getAnyOpenWorkspaceLocation(IWorkspaceRoot root) { int numMappings = WORKSPACE_MAPPINGS.size(getNd(), this.address); for (int mapping = 0; mapping < numMappings; mapping++) { NdWorkspaceLocation nextMapping = WORKSPACE_MAPPINGS.get(getNd(), this.address, mapping); IPath nextPath = new Path(nextMapping.getPath().getString()); if (nextPath.isEmpty()) { continue; } IProject project = root.getProject(nextPath.segment(0)); if (project.isOpen()) { return nextPath; } } return Path.EMPTY; } /** * Returns a workspace path to this resource if possible and the absolute filesystem location if not. */ public IPath getPath() { IPath workspacePath = getFirstWorkspaceLocation(); if (workspacePath.isEmpty()) { return new Path(getLocation().getString()); } return workspacePath; } public List<NdWorkspaceLocation> getWorkspaceMappings() { return WORKSPACE_MAPPINGS.asList(getNd(), this.address); } public IString getLocation() { return FILENAME.get(getNd(), this.address); } public void setLocation(String filename) { FILENAME.put(getNd(), this.address, filename); } public FileFingerprint getFingerprint() { return new FileFingerprint( getTimeLastScanned(), getSizeLastScanned(), getHashcodeLastScanned()); } private long getHashcodeLastScanned() { return HASHCODE_LAST_SCANNED.get(getNd(), this.address); } /** * Returns true iff the indexer has finished writing the contents of this file to the index. Returns false if * indexing may still be going on. If this returns false, readers should ignore all contents of this file. * * @return true iff the contents of this file are usable */ public boolean isDoneIndexing() { return getTimeLastScanned() != 0; } public long getTimeLastScanned() { return TIME_LAST_SCANNED.get(getNd(), this.address); } public long getSizeLastScanned() { return SIZE_LAST_SCANNED.get(getNd(), this.address); } public long getTimeLastUsed() { return TIME_LAST_USED.get(getNd(), this.address); } public void setTimeLastUsed(long timeLastUsed) { TIME_LAST_USED.put(getNd(), this.address, timeLastUsed); } public void setFingerprint(FileFingerprint newFingerprint) { TIME_LAST_SCANNED.put(getNd(), this.address, newFingerprint.getTime()); HASHCODE_LAST_SCANNED.put(getNd(), this.address, newFingerprint.getHash()); SIZE_LAST_SCANNED.put(getNd(), this.address, newFingerprint.getSize()); } public void setPackageFragmentRoot(char[] javaRoot) { JAVA_ROOT.put(getNd(), this.address, javaRoot); } /** * Returns the absolute path to the java root for this .jar or .class file. If this is a .jar file, it returns its * own filename. */ public IString getPackageFragmentRoot() { IString javaRoot = JAVA_ROOT.get(getNd(), this.address); if (javaRoot.length() == 0) { return getLocation(); } return javaRoot; } public void markAsInvalid() { TIME_LAST_SCANNED.put(getNd(), this.address, 0); } public int getBindingCount() { return ALL_NODES.size(getNd(), this.address); } public List<NdBinding> getBindings() { return ALL_NODES.asList(getNd(), this.address); } public NdBinding getBinding(int index) { return ALL_NODES.get(getNd(), this.address, index); } public String toString() { try { return FILENAME.get(getNd(), this.address).toString(); } catch (RuntimeException e) { // This is called most often from the debugger, so we want to return something meaningful even // if the code is buggy, the database is corrupt, or we don't have a read lock. return super.toString(); } } }