| /** |
| * 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; |
| } |
| } |
| } |