/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.modules.ruby.elements;
import java.io.File;
import java.net.MalformedURLException;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import javax.swing.text.Document;
import org.netbeans.modules.csl.api.Modifier;
import org.netbeans.modules.csl.spi.GsfUtilities;
import org.netbeans.modules.parsing.spi.indexing.support.IndexResult;
import org.netbeans.modules.ruby.RubyIndex;
import org.netbeans.modules.ruby.RubyType;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
/**
* A program element coming from the persistent index.
*
* @author Tor Norbye
*/
public abstract class IndexedElement extends RubyElement {
/** This method is documented */
public static final int DOCUMENTED = 1 << 0;
/** This method is protected */
public static final int PROTECTED = 1 << 1;
/** This method is private */
public static final int PRIVATE = 1 << 2;
/** This method is top level (implicit member of Object) */
public static final int TOPLEVEL = 1 << 3;
/** This element is "static" (e.g. it's a classvar for fields, class method for methods etc) */
public static final int STATIC = 1 << 4;
/** This element is deliberately not documented (rdoc :nodoc:) */
public static final int NODOC = 1 << 5;
/** This element is virtual, i.e. doesn't have a direct match in sources/AST*/
public static final int VIRTUAL = 1 << 9;
protected final FileObject file;
protected final String clz;
protected final String fqn;
protected final String require;
protected final String attributes;
protected final int flags;
protected int docLength = -1;
private Set<Modifier> modifiers;
private final RubyIndex index;
private Document document;
private final FileObject context;
protected RubyType type;
protected IndexedElement(RubyIndex index, FileObject file, String fqn,
String clz, String require, String attributes,
int flags, FileObject context, RubyType type) {
this.index = index;
this.file = file;
this.fqn = fqn;
this.require = require;
this.attributes = attributes;
// XXX Why do methods need to know their clz (since they already have fqn)
this.clz = clz;
this.flags = flags;
this.context = context;
this.type = type;
}
protected IndexedElement(RubyIndex index, IndexResult result, String fqn,
String clz, String require, String attributes,
int flags, FileObject context, RubyType type) {
this(index, result.getFile(), fqn, clz, require, attributes, flags, context, type);
}
protected IndexedElement(RubyIndex index, IndexResult result, String fqn,
String clz, String require, String attributes,
int flags, FileObject context) {
this(index, result, fqn, clz, require, attributes, flags, context, null);
}
public abstract String getSignature();
public final String getRequire() {
return require;
}
public final String getFqn() {
return fqn;
}
@Override
public RubyType getType() {
if (type == null && attributes != null) {
int lastSemiColon = attributes.lastIndexOf(';');
if (lastSemiColon != -1) {
int last2SemiColon = attributes.lastIndexOf(';', lastSemiColon - 1);
if (lastSemiColon != -1) {
String typesS = attributes.substring(last2SemiColon + 1, lastSemiColon);
type = parseTypes(typesS);
}
}
}
if (type == null) {
type = RubyType.unknown();
}
return type;
}
private RubyType parseTypes(final String types) {
if (types.length() == 0) {
return RubyType.unknown();
}
if (!types.contains("|")) { // just one type
return RubyType.create(types);
}
return new RubyType(types.split("\\|")); // NOI18N
}
@Override
public String toString() {
return getSignature();
}
public final String getClz() {
return clz;
}
public RubyIndex getIndex() {
return index;
}
@Override
public String getIn() {
return getClz();
}
public Document getDocument() {
if (document == null) {
FileObject fo = getFileObject();
if (fo == null) {
return null;
}
document = GsfUtilities.getDocument(fo, true);
}
return document;
}
// public ParserFile getFile() {
// boolean platform = false; // XXX FIND OUT WHAT IT IS!
//
// return new DefaultParserFile(getFileObject(), null, platform);
// }
@Override
public FileObject getFileObject() {
return file;
}
public String getFileUrl() {
if (file == null) {
// there's no file for e.g. for dynamic methods
return null;
}
File f = FileUtil.toFile(file);
try {
return f == null ? null : f.toURI().toURL().toExternalForm();
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
@Override
public final Set<Modifier> getModifiers() {
if (modifiers == null) {
Modifier access = Modifier.PUBLIC;
if (isPrivate()) {
access = Modifier.PRIVATE;
} else if (isProtected()) {
access = Modifier.PROTECTED;
}
boolean isStatic = isStatic();
if (access != Modifier.PUBLIC) {
if (isStatic) {
modifiers = EnumSet.of(access, Modifier.STATIC);
} else {
modifiers = EnumSet.of(access);
}
} else if (isStatic) {
modifiers = EnumSet.of(Modifier.STATIC);
} else {
modifiers = Collections.emptySet();
}
}
return modifiers;
}
/** Return the length of the documentation for this class, in characters */
public int getDocumentationLength() {
return isDocumented() ? 1 : 0;
}
/** Return a string (suitable for persistence) encoding the given flags */
public static char flagToFirstChar(int flags) {
char first = (char)(flags >>= 4);
if (first >= 10) {
return (char)(first-10+'a');
} else {
return (char)(first+'0');
}
}
/** Return a string (suitable for persistence) encoding the given flags */
public static char flagToSecondChar(int flags) {
char second = (char)(flags & 0xf);
if (second >= 10) {
return (char)(second-10+'a');
} else {
return (char)(second+'0');
}
}
/** Return a string (suitable for persistence) encoding the given flags */
public static String flagToString(int flags) {
return (""+flagToFirstChar(flags)) + flagToSecondChar(flags);
}
/** Return flag corresponding to the given encoding chars */
public static int stringToFlag(String s, int startIndex) {
return stringToFlag(s.charAt(startIndex), s.charAt(startIndex+1));
}
/** Return flag corresponding to the given encoding chars */
public static int stringToFlag(char first, char second) {
if (first == ';'){
return 0;
}
int high = 0;
int low = 0;
if (first > '9') {
high = first-'a'+10;
} else {
high = first-'0';
}
if (second > '9') {
low = second-'a'+10;
} else {
low = second-'0';
}
return (high << 4) + low;
}
public boolean isDocumented() {
return (flags & DOCUMENTED) != 0;
}
public boolean isPublic() {
return (flags & PRIVATE & PROTECTED) == 0;
}
public boolean isPrivate() {
// XXX hmmm not symmetric, see what the old semantics was for why I needed both?
return ((flags & PRIVATE) != 0) || ((flags & PROTECTED) != 0);
}
public boolean isProtected() {
return (flags & PROTECTED) != 0;
}
public boolean isTopLevel() {
return (flags & TOPLEVEL) != 0;
}
public boolean isStatic() {
return (flags & STATIC) != 0;
}
public boolean isNoDoc() {
return (flags & NODOC) != 0;
}
/**
* @see #VIRTUAL
*/
public boolean isVirtual() {
return (flags & VIRTUAL) != 0;
}
// For testsuite
public static String decodeFlags(int flags) {
StringBuilder sb = new StringBuilder();
if ((flags & DOCUMENTED) != 0) {
sb.append("|DOCUMENTED");
}
if ((flags & PRIVATE) != 0) {
sb.append("|PRIVATE");
}
if ((flags & PROTECTED) != 0) {
sb.append("|PROTECTED");
}
if ((flags & TOPLEVEL) != 0) {
sb.append("|TOPLEVEL");
}
if ((flags & STATIC) != 0) {
sb.append("|STATIC");
}
if ((flags & NODOC) != 0) {
sb.append("|NODOC");
}
if ((flags & VIRTUAL) != 0) {
sb.append("|VIRTUAL");
}
return sb.toString();
}
// For testsuite
public static int stringToFlags(String string) {
int flags = 0;
if (string.indexOf("|DOCUMENTED") != -1) {
flags += DOCUMENTED;
}
if (string.indexOf("|PRIVATE") != -1) {
flags += PRIVATE;
}
if (string.indexOf("|PROTECTED") != -1) {
flags += PROTECTED;
}
if (string.indexOf("|TOPLEVEL") != -1) {
flags += TOPLEVEL;
}
if (string.indexOf("|STATIC") != -1) {
flags += STATIC;
}
if (string.indexOf("|NODOC") != -1) {
flags += NODOC;
}
if (string.indexOf("|VIRTUAL") != -1) {
flags += VIRTUAL;
}
return flags;
}
/**
* Returns whether this method pertains to the Module class, which is handle
* in a kind of special manner in Ruby.
*
* @return whether the element is declared in the Module
*/
public boolean doesBelongToModule() {
return "Module".equals(getFqn()); // NOI18N
}
}