blob: a6d1be9a3e9c4a2b9ff0771f9516fec780fd78ca [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.netbeans.modules.db.metadata.model.api;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.openide.util.Parameters;
/**
* Represents the handle of a metadata element.
*
* <p>Metadata elements cannot escape the {@link MetadataModel#runReadAction} method.
* Handles can be used to pass information about metadata elements out of this method.
* The handle can be {@link #resolve resolved} to the corresponding
* metadata element in another {@code runReadAction} method.</p>
*
* @param <T> the type of the metadata element that this handle was created for.
*
* @author Andrei Badea
*/
public class MetadataElementHandle<T extends MetadataElement> {
// These integers place a particular element in the hierarchy of elements,
// with CATALOG at the top
private static final int CATALOG = 0;
private static final int SCHEMA = 1;
private static final int TABLE = 2;
private static final int VIEW = 2;
private static final int PROCEDURE = 2;
private static final int COLUMN = 3;
private static final int PARAMETER = 3;
private static final int INDEX = 3;
private static final int FOREIGN_KEY = 3;
private static final int FOREIGN_KEY_COLUMN = 4;
private static final int INDEX_COLUMN = 4;
private static final int FUNCTION = 2;
// The hierarchy of names for this element (e.g. ["mycatalog","myschema","mytable","mycolumn"])
//
// It is the combination of the hierarchy of names and kinds that uniquely identifies this
// element in the metadata model.
private final String[] names;
// The hierarchy of element kinds for this element (e.g. [CATALOG,SCHEMA,TABLE,COLUMN]
// or [CATALOG,SCHEMA,VIEW,COLUMN])
//
// It is the combination of the hierarchy of names and kinds that uniquely identifies this
// element in the metadata model.
private final Kind[] kinds;
/**
* Creates a handle for a metadata element.
*
* @param <T> the type of the metadata element to create this handle for.
* @param element a metadata element.
* @return the handle for the given metadata element.
*/
public static <T extends MetadataElement> MetadataElementHandle<T> create(T element) {
Parameters.notNull("element", element);
List<String> names = new ArrayList<String>();
List<Kind> kinds = new ArrayList<Kind>();
MetadataElement current = element;
while (current != null) {
names.add(current.getInternalName());
kinds.add(Kind.of(current));
current = current.getParent();
}
Collections.reverse(names);
Collections.reverse(kinds);
String[] namesArray = names.toArray(new String[names.size()]);
Kind[] kindsArray = kinds.toArray(new Kind[kinds.size()]);
return new MetadataElementHandle<T>(namesArray,kindsArray);
}
// For use in unit tests.
static <T extends MetadataElement> MetadataElementHandle<T> create(Class<T> clazz, String[] names, Kind[] kinds) {
return new MetadataElementHandle<T>(names, kinds);
}
private MetadataElementHandle(String[] names, Kind[] kinds) {
this.names = names;
this.kinds = kinds;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MetadataElementHandle<?> other = (MetadataElementHandle<?>) obj;
if (!Arrays.equals(this.kinds, other.kinds)) {
return false;
}
if (!Arrays.equals(this.names, other.names)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
for (String name : names) {
if (name != null) {
hash ^= name.hashCode();
} else {
// Do not XOR with a constant, since multiple nulls would
// cause the hash to just flip-flop between two values.
hash++;
}
}
for (Kind kind : kinds) {
hash ^= kind.hashCode();
}
return hash;
}
/**
* Resolves this handle to the corresponding metadata element, if any.
*
* @param metadata the {@link Metadata} instance to resolve this element against.
* @return the corresponding metadata element or null if it could not be found
* (for example because it is not present in the given {@code Metadata}
* instance, or because it has been removed).
*/
@SuppressWarnings("unchecked")
public T resolve(Metadata metadata) {
int length = kinds.length;
switch (kinds[length - 1]) {
case CATALOG:
return (T) resolveCatalog(metadata);
case SCHEMA:
return (T) resolveSchema(metadata);
case TABLE:
return (T) resolveTable(metadata);
case VIEW:
return (T) resolveView(metadata);
case PROCEDURE:
return (T) resolveProcedure(metadata);
case COLUMN:
return (T) resolveColumn(metadata);
case PRIMARY_KEY:
return (T) resolvePrimaryKey(metadata);
case PARAMETER:
return (T) resolveParameter(metadata);
case FOREIGN_KEY:
return (T) resolveForeignKey(metadata);
case INDEX:
return (T) resolveIndex(metadata);
case FOREIGN_KEY_COLUMN:
return (T) resolveForeignKeyColumn(metadata);
case INDEX_COLUMN:
return (T) resolveIndexColumn(metadata);
case RETURN_VALUE:
return (T) resolveReturnValue(metadata);
case FUNCTION:
return (T) resolveFunction(metadata);
default:
throw new IllegalStateException("Unhandled kind " + kinds[kinds.length -1]);
}
}
private Catalog resolveCatalog(Metadata metadata) {
return metadata.getCatalog(names[CATALOG]);
}
private Schema resolveSchema(Metadata metadata) {
Catalog catalog = resolveCatalog(metadata);
if (catalog != null) {
String name = names[SCHEMA];
if (name != null) {
return catalog.getSchema(name);
} else {
return catalog.getSyntheticSchema();
}
}
return null;
}
private Table resolveTable(Metadata metadata) {
Schema schema = resolveSchema(metadata);
if (schema != null) {
return schema.getTable(names[TABLE]);
}
return null;
}
private View resolveView(Metadata metadata) {
Schema schema = resolveSchema(metadata);
if (schema != null) {
return schema.getView(names[VIEW]);
}
return null;
}
private Procedure resolveProcedure(Metadata metadata) {
Schema schema = resolveSchema(metadata);
if (schema != null && kinds[PROCEDURE] == Kind.PROCEDURE) {
return schema.getProcedure(names[PROCEDURE]);
}
return null;
}
private Function resolveFunction(Metadata metadata) {
Schema schema = resolveSchema(metadata);
if (schema != null && kinds[FUNCTION] == Kind.FUNCTION) {
return schema.getFunction(names[FUNCTION]);
}
return null;
}
private Value resolveReturnValue(Metadata metadata) {
Function proc = resolveFunction(metadata);
if (proc != null) {
return proc.getReturnValue();
}
Procedure proc2 = resolveProcedure(metadata);
if (proc2 != null) {
return proc2.getReturnValue();
}
return null;
}
private PrimaryKey resolvePrimaryKey(Metadata metadata) {
Table table = resolveTable(metadata);
if (table != null) {
return table.getPrimaryKey();
}
return null;
}
private Column resolveColumn(Metadata metadata) {
// A column can be part of a number of different metadata elements.
// Find out which one and resolve appropriately
switch (kinds[COLUMN - 1]) {
case TABLE:
Table table = resolveTable(metadata);
if (table != null) {
return table.getColumn(names[COLUMN]);
}
return null;
case PROCEDURE:
Procedure proc = resolveProcedure(metadata);
if (proc != null) {
return proc.getColumn(names[COLUMN]);
}
return null;
case VIEW:
View view = resolveView(metadata);
if (view != null) {
return view.getColumn(names[COLUMN]);
}
return null;
default:
throw new IllegalStateException("Unhandled kind " + kinds[COLUMN -1]);
}
}
private Parameter resolveParameter(Metadata metadata) {
Procedure proc = resolveProcedure(metadata);
if (proc != null) {
return proc.getParameter(names[PARAMETER]);
}
Function proc2 = resolveFunction(metadata);
if (proc2 != null) {
return proc2.getParameter(names[PARAMETER]);
}
return null;
}
private Index resolveIndex(Metadata metadata) {
Table table = resolveTable(metadata);
if (table != null) {
return table.getIndex(names[INDEX]);
}
return null;
}
private ForeignKey resolveForeignKey(Metadata metadata) {
Table table = resolveTable(metadata);
if (table != null) {
return table.getForeignKeyByInternalName(names[FOREIGN_KEY]);
}
return null;
}
private ForeignKeyColumn resolveForeignKeyColumn(Metadata metadata) {
ForeignKey key = resolveForeignKey(metadata);
if (key != null) {
return key.getColumn(names[FOREIGN_KEY_COLUMN]);
}
return null;
}
private IndexColumn resolveIndexColumn(Metadata metadata) {
Index index = resolveIndex(metadata);
if (index != null) {
return index.getColumn(names[INDEX_COLUMN]);
}
return null;
}
// Not private becuase it's used in unit tests
enum Kind {
CATALOG(Catalog.class),
SCHEMA(Schema.class),
TABLE(Table.class),
VIEW(View.class),
PROCEDURE(Procedure.class),
PARAMETER(Parameter.class),
COLUMN(Column.class),
PRIMARY_KEY(PrimaryKey.class),
FOREIGN_KEY(ForeignKey.class),
INDEX(Index.class),
FOREIGN_KEY_COLUMN(ForeignKeyColumn.class),
INDEX_COLUMN(IndexColumn.class),
RETURN_VALUE(Value.class),
FUNCTION(Function.class);
public static Kind of(MetadataElement element) {
return of(element.getClass());
}
public static Kind of(Class<? extends MetadataElement> clazz) {
for (Kind kind : Kind.values()) {
if (kind.clazz.equals(clazz)) {
return kind;
}
}
throw new IllegalStateException("Unhandled class " + clazz);
}
private final Class<? extends MetadataElement> clazz;
private Kind(Class<? extends MetadataElement> clazz) {
this.clazz = clazz;
}
}
}