Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEike Stepper2013-03-08 02:54:13 -0500
committerEike Stepper2013-03-08 02:54:13 -0500
commitef293640557a8709debf62f11e2fad7841af984a (patch)
tree48e2b1e14c974655326cd87fb3ed581a920e5dde
parent549c7bf0b7c58f0cd49eab63149dd79654f1451e (diff)
downloadcdo-ef293640557a8709debf62f11e2fad7841af984a.tar.gz
cdo-ef293640557a8709debf62f11e2fad7841af984a.tar.xz
cdo-ef293640557a8709debf62f11e2fad7841af984a.zip
[401763] Make CDO Server more robust against data dictionary changes
https://bugs.eclipse.org/bugs/show_bug.cgi?id=401763
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStore.java3
-rw-r--r--plugins/org.eclipse.net4j.db.tests/src/org/eclipse/net4j/db/tests/AbstractDBTest.java41
-rw-r--r--plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/IDBDatabase.java8
-rw-r--r--plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/ddl/delta/IDBDeltaVisitor.java249
-rw-r--r--plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/DBDatabase.java29
-rw-r--r--plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/DBSchemaTransaction.java11
-rw-r--r--plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/ddl/delta/DBDelta.java11
-rw-r--r--plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/ddl/delta/DBTableDelta.java2
-rw-r--r--plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/Pair.java24
9 files changed, 319 insertions, 59 deletions
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStore.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStore.java
index 38245e4917..db1532f447 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStore.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStore.java
@@ -46,7 +46,6 @@ import org.eclipse.net4j.db.IDBDatabase;
import org.eclipse.net4j.db.ddl.IDBField;
import org.eclipse.net4j.db.ddl.IDBSchema;
import org.eclipse.net4j.db.ddl.IDBTable;
-import org.eclipse.net4j.db.ddl.delta.IDBDelta;
import org.eclipse.net4j.spi.db.DBSchema;
import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
@@ -625,7 +624,7 @@ public class DBStore extends Store implements IDBStore, CDOAllRevisionsProvider
IDBSchema schema = createSchema();
database = DBUtil.openDatabase(dbAdapter, dbConnectionProvider, repository.getName());
- database.ensureSchema(schema, IDBDelta.ChangeKind.ADD);
+ database.ensureSchema(schema);
LifecycleUtil.activate(idHandler);
LifecycleUtil.activate(metaDataManager);
diff --git a/plugins/org.eclipse.net4j.db.tests/src/org/eclipse/net4j/db/tests/AbstractDBTest.java b/plugins/org.eclipse.net4j.db.tests/src/org/eclipse/net4j/db/tests/AbstractDBTest.java
index 03c3403baa..0843467764 100644
--- a/plugins/org.eclipse.net4j.db.tests/src/org/eclipse/net4j/db/tests/AbstractDBTest.java
+++ b/plugins/org.eclipse.net4j.db.tests/src/org/eclipse/net4j/db/tests/AbstractDBTest.java
@@ -116,13 +116,13 @@ public abstract class AbstractDBTest extends AbstractOMTest
assertEquals(count, tables.size());
}
- public void testSchemaEmpty() throws Exception
+ public void testReadSchema() throws Exception
{
IDBSchema schema = DBUtil.readSchema(adapter, getConnection(), SCHEMA_NAME);
assertEquals(true, schema.isEmpty());
}
- public void testSchemaCreation() throws Exception
+ public void testCreateSchema() throws Exception
{
IDBDatabase database = DBUtil.openDatabase(adapter, connectionProvider, SCHEMA_NAME);
IDBSchema schema = database.getSchema();
@@ -183,7 +183,7 @@ public abstract class AbstractDBTest extends AbstractOMTest
assertEquals(index23.getType(), schema.getTables()[1].getIndex(2).getType());
}
- public void testSchemaAddition() throws Exception
+ public void testChangeSchema() throws Exception
{
// Init database
IDBDatabase database = DBUtil.openDatabase(adapter, connectionProvider, SCHEMA_NAME);
@@ -283,4 +283,39 @@ public abstract class AbstractDBTest extends AbstractOMTest
assertEquals(index23, schema2.getTables()[1].getIndex(2));
assertEquals(index23.getType(), schema2.getTables()[1].getIndex(2).getType());
}
+
+ public void testEnsureSchema() throws Exception
+ {
+ // Init database
+ IDBDatabase database = DBUtil.openDatabase(adapter, connectionProvider, SCHEMA_NAME);
+ IDBSchemaTransaction schemaTransaction = database.openSchemaTransaction();
+ IDBSchema workingCopy = schemaTransaction.getSchema();
+
+ IDBTable table1 = workingCopy.addTable("table1");
+ table1.addField("field11", DBType.INTEGER, true);
+ table1.addField("field12", DBType.VARCHAR, 64, true);
+ table1.addField("field13", DBType.BOOLEAN);
+
+ schemaTransaction.commit();
+
+ IDBSchema newSchema = DBUtil.createSchema("bla");
+ IDBTable table2 = newSchema.addTable("table2");
+ IDBField field21 = table2.addField("field21", DBType.INTEGER, true);
+ IDBField field22 = table2.addField("field22", DBType.VARCHAR, 64, true);
+ table2.addField("field23", DBType.BOOLEAN);
+ table2.addIndex("index21", IDBIndex.Type.PRIMARY_KEY, field21, field22);
+ table2.addIndex("index22", IDBIndex.Type.UNIQUE, field21, field22);
+ table2.addIndex("index23", IDBIndex.Type.NON_UNIQUE, field22);
+
+ // Reload database
+ IDBDatabase database2 = DBUtil.openDatabase(adapter, connectionProvider, SCHEMA_NAME);
+ database2.ensureSchema(newSchema);
+
+ IDBSchema schema2 = database2.getSchema();
+ assertEquals(true, schema2.isLocked());
+ assertEquals(false, schema2.isEmpty());
+ assertEquals(2, schema2.getTables().length);
+
+ DBUtil.dump(schema2);
+ }
}
diff --git a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/IDBDatabase.java b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/IDBDatabase.java
index a3ed4dd563..ef313c5004 100644
--- a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/IDBDatabase.java
+++ b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/IDBDatabase.java
@@ -11,12 +11,10 @@
package org.eclipse.net4j.db;
import org.eclipse.net4j.db.ddl.IDBSchema;
-import org.eclipse.net4j.db.ddl.delta.IDBDelta.ChangeKind;
+import org.eclipse.net4j.db.ddl.delta.IDBDeltaVisitor;
import org.eclipse.net4j.util.collection.Closeable;
import org.eclipse.net4j.util.container.IContainer;
-import java.util.Map;
-
/**
* @author Eike Stepper
* @noimplement This interface is not intended to be implemented by clients.
@@ -37,7 +35,9 @@ public interface IDBDatabase extends IContainer<IDBTransaction>, Closeable
public IDBSchemaTransaction getSchemaTransaction();
- public void ensureSchema(IDBSchema schema, Map<ChangeKind, Boolean> policy);
+ public void ensureSchema(IDBSchema schema, IDBDeltaVisitor.Filter.Policy policy);
+
+ public void ensureSchema(IDBSchema schema);
public IDBTransaction openTransaction();
diff --git a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/ddl/delta/IDBDeltaVisitor.java b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/ddl/delta/IDBDeltaVisitor.java
index 82a8092411..1bd3bf2b0a 100644
--- a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/ddl/delta/IDBDeltaVisitor.java
+++ b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/db/ddl/delta/IDBDeltaVisitor.java
@@ -20,11 +20,11 @@ import org.eclipse.net4j.internal.db.ddl.delta.DBIndexFieldDelta;
import org.eclipse.net4j.internal.db.ddl.delta.DBPropertyDelta;
import org.eclipse.net4j.internal.db.ddl.delta.DBSchemaDelta;
import org.eclipse.net4j.internal.db.ddl.delta.DBTableDelta;
+import org.eclipse.net4j.util.collection.Pair;
-import java.util.Collections;
+import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
-import java.util.Set;
/**
* @since 4.2
@@ -347,30 +347,21 @@ public interface IDBDeltaVisitor
*/
public static class Filter extends Default
{
- public static final Map<ChangeKind, Boolean> DEFAULT_POLICY = createPolicy(null, null);
+ public static final Policy DEFAULT_POLICY = new Policy().allowAll().freeze();
- public static final Boolean ALLOWED = Boolean.TRUE;
-
- public static final Boolean FORBIDDEN = Boolean.FALSE;
-
- private Map<ChangeKind, Boolean> policy;
+ private Policy policy;
public Filter()
{
this(null);
}
- public Filter(Map<ChangeKind, Boolean> policy)
+ public Filter(Policy policy)
{
this.policy = policy == null ? DEFAULT_POLICY : policy;
}
- public Filter(Set<ChangeKind> ignoredChanges, Set<ChangeKind> forbiddenChanges)
- {
- this(createPolicy(ignoredChanges, forbiddenChanges));
- }
-
- public final Map<ChangeKind, Boolean> getPolicy()
+ public final Policy getPolicy()
{
return policy;
}
@@ -516,42 +507,231 @@ public interface IDBDeltaVisitor
@Override
protected final boolean handle(IDBDelta delta)
{
- Boolean deltaPolicy = policy.get(delta.getChangeKind());
- if (deltaPolicy == FORBIDDEN)
+ if (policy.isForbidden(delta))
{
throw new ForbiddenChangeException(delta);
}
- return deltaPolicy == ALLOWED;
+ if (policy.isAllowed(delta))
+ {
+ return true;
+ }
+
+ return false;
}
- public static Map<ChangeKind, Boolean> createPolicy(Set<ChangeKind> ignoredChanges, Set<ChangeKind> forbiddenChanges)
+ /**
+ * @author Eike Stepper
+ */
+ public static final class Policy implements Serializable
{
- if (ignoredChanges == null)
+ public static final Object ALLOWED = "ALLOWED";
+
+ public static final Object FORBIDDEN = "FORBIDDEN";
+
+ public static final Object IGNORED = "IGNORED";
+
+ private static final long serialVersionUID = 1L;
+
+ private Map<Object, Object> clauses = new HashMap<Object, Object>();
+
+ private transient boolean frozen;
+
+ public Policy()
+ {
+ }
+
+ public boolean isAllowed(DeltaType deltaType)
{
- ignoredChanges = Collections.emptySet();
+ Object value = clauses.get(deltaType);
+ return value == ALLOWED;
}
- if (forbiddenChanges == null)
+ public boolean isAllowed(ChangeKind changeKind)
{
- forbiddenChanges = Collections.emptySet();
+ Object value = clauses.get(changeKind);
+ return value == ALLOWED;
}
- Map<ChangeKind, Boolean> policy = new HashMap<IDBDelta.ChangeKind, Boolean>();
- for (ChangeKind changeKind : ChangeKind.values())
+ public boolean isAllowed(DeltaType deltaType, ChangeKind changeKind)
{
- if (!ignoredChanges.contains(changeKind))
+ Object value = clauses.get(Pair.create(deltaType, changeKind));
+ if (value == null)
{
- policy.put(changeKind, ALLOWED);
+ value = clauses.get(deltaType);
+ if (value == null)
+ {
+ value = clauses.get(changeKind);
+ }
}
+
+ return value == ALLOWED;
}
- for (ChangeKind changeKind : forbiddenChanges)
+ public boolean isAllowed(IDBDelta delta)
{
- policy.put(changeKind, FORBIDDEN);
+ return isAllowed(delta.getDeltaType(), delta.getChangeKind());
}
- return policy;
+ public boolean isForbidden(DeltaType deltaType)
+ {
+ Object value = clauses.get(deltaType);
+ return value == FORBIDDEN;
+ }
+
+ public boolean isForbidden(ChangeKind changeKind)
+ {
+ Object value = clauses.get(changeKind);
+ return value == FORBIDDEN;
+ }
+
+ public boolean isForbidden(DeltaType deltaType, ChangeKind changeKind)
+ {
+ Object value = clauses.get(Pair.create(deltaType, changeKind));
+ if (value == null)
+ {
+ value = clauses.get(deltaType);
+ if (value == null)
+ {
+ value = clauses.get(changeKind);
+ }
+ }
+
+ return value == FORBIDDEN;
+ }
+
+ public boolean isForbidden(IDBDelta delta)
+ {
+ return isForbidden(delta.getDeltaType(), delta.getChangeKind());
+ }
+
+ public boolean isIgnored(DeltaType deltaType)
+ {
+ Object value = clauses.get(deltaType);
+ return value == null || value == IGNORED;
+ }
+
+ public boolean isIgnored(ChangeKind changeKind)
+ {
+ Object value = clauses.get(changeKind);
+ return value == null || value == IGNORED;
+ }
+
+ public boolean isIgnored(DeltaType deltaType, ChangeKind changeKind)
+ {
+ Object value = clauses.get(Pair.create(deltaType, changeKind));
+ if (value == null)
+ {
+ value = clauses.get(deltaType);
+ if (value == null)
+ {
+ value = clauses.get(changeKind);
+ }
+ }
+
+ return value == null || value == IGNORED;
+ }
+
+ public boolean isIgnored(IDBDelta delta)
+ {
+ return isIgnored(delta.getDeltaType(), delta.getChangeKind());
+ }
+
+ public Policy allow(DeltaType deltaType)
+ {
+ return addClause(deltaType, ALLOWED);
+ }
+
+ public Policy allow(ChangeKind changeKind)
+ {
+ return addClause(changeKind, ALLOWED);
+ }
+
+ public Policy allow(DeltaType deltaType, ChangeKind changeKind)
+ {
+ return addClause(Pair.create(deltaType, changeKind), ALLOWED);
+ }
+
+ public Policy allowAll()
+ {
+ return ignoreAll().allow(ChangeKind.ADD).allow(ChangeKind.REMOVE).allow(ChangeKind.CHANGE);
+ }
+
+ public Policy forbid(DeltaType deltaType)
+ {
+ return addClause(deltaType, FORBIDDEN);
+ }
+
+ public Policy forbid(ChangeKind changeKind)
+ {
+ return addClause(changeKind, FORBIDDEN);
+ }
+
+ public Policy forbid(DeltaType deltaType, ChangeKind changeKind)
+ {
+ return addClause(Pair.create(deltaType, changeKind), FORBIDDEN);
+ }
+
+ public Policy forbidAll()
+ {
+ return ignoreAll().forbid(ChangeKind.ADD).forbid(ChangeKind.REMOVE).forbid(ChangeKind.CHANGE);
+ }
+
+ public Policy ignore(DeltaType deltaType)
+ {
+ return removeClause(deltaType);
+ }
+
+ public Policy ignore(ChangeKind changeKind)
+ {
+ return removeClause(changeKind);
+ }
+
+ public Policy ignore(DeltaType deltaType, ChangeKind changeKind)
+ {
+ return removeClause(Pair.create(deltaType, changeKind));
+ }
+
+ public Policy ignoreAll()
+ {
+ checkFrozen();
+ clauses.clear();
+ return this;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Policy" + clauses;
+ }
+
+ public Policy freeze()
+ {
+ frozen = true;
+ return this;
+ }
+
+ private void checkFrozen()
+ {
+ if (frozen)
+ {
+ throw new IllegalStateException("Policy is frozen: " + this);
+ }
+ }
+
+ private Policy addClause(Object key, Object value)
+ {
+ checkFrozen();
+ clauses.put(key, value);
+ return this;
+ }
+
+ private Policy removeClause(Object key)
+ {
+ checkFrozen();
+ clauses.remove(key);
+ return this;
+ }
}
/**
@@ -587,14 +767,9 @@ public interface IDBDeltaVisitor
{
}
- public Copier(Map<ChangeKind, Boolean> policies)
- {
- super(policies);
- }
-
- public Copier(Set<ChangeKind> ignoredChanges, Set<ChangeKind> forbiddenChanges)
+ public Copier(Policy policy)
{
- super(ignoredChanges, forbiddenChanges);
+ super(policy);
}
public final IDBSchemaDelta getResult()
diff --git a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/DBDatabase.java b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/DBDatabase.java
index 9cef71223d..c1fac3c719 100644
--- a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/DBDatabase.java
+++ b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/DBDatabase.java
@@ -18,7 +18,9 @@ import org.eclipse.net4j.db.IDBSchemaTransaction;
import org.eclipse.net4j.db.IDBTransaction;
import org.eclipse.net4j.db.ddl.IDBSchema;
import org.eclipse.net4j.db.ddl.delta.IDBDelta.ChangeKind;
+import org.eclipse.net4j.db.ddl.delta.IDBDelta.DeltaType;
import org.eclipse.net4j.db.ddl.delta.IDBDeltaVisitor;
+import org.eclipse.net4j.db.ddl.delta.IDBDeltaVisitor.Filter.Policy;
import org.eclipse.net4j.db.ddl.delta.IDBSchemaDelta;
import org.eclipse.net4j.spi.db.DBAdapter;
import org.eclipse.net4j.spi.db.DBSchema;
@@ -28,13 +30,15 @@ import org.eclipse.net4j.util.container.SetContainer;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedList;
-import java.util.Map;
/**
* @author Eike Stepper
*/
public final class DBDatabase extends SetContainer<IDBTransaction> implements IDBDatabase
{
+ private static final Policy DEFAULT_ENSURE_SCHEMA_POLICY = //
+ new IDBDeltaVisitor.Filter.Policy().allow(DeltaType.SCHEMA, ChangeKind.CHANGE).allow(ChangeKind.ADD).freeze();
+
private DBAdapter adapter;
private IDBConnectionProvider connectionProvider;
@@ -120,12 +124,7 @@ public final class DBDatabase extends SetContainer<IDBTransaction> implements ID
return schemaTransaction;
}
- public void ensureSchema(IDBSchema schema)
- {
- ensureSchema(schema, null);
- }
-
- public void ensureSchema(IDBSchema schema, Map<ChangeKind, Boolean> policy)
+ public void ensureSchema(IDBSchema schema, IDBDeltaVisitor.Filter.Policy policy)
{
IDBSchemaTransaction schemaTransaction = null;
@@ -152,6 +151,11 @@ public final class DBDatabase extends SetContainer<IDBTransaction> implements ID
}
}
+ public void ensureSchema(IDBSchema schema)
+ {
+ ensureSchema(schema, DEFAULT_ENSURE_SCHEMA_POLICY);
+ }
+
public DBTransaction openTransaction()
{
DBTransaction transaction = new DBTransaction(this);
@@ -283,6 +287,12 @@ public final class DBDatabase extends SetContainer<IDBTransaction> implements ID
{
return --readers > 0;
}
+
+ @Override
+ public String toString()
+ {
+ return "READERS[" + readers + "]";
+ }
}
/**
@@ -290,5 +300,10 @@ public final class DBDatabase extends SetContainer<IDBTransaction> implements ID
*/
public final class WriteSchemaAccess implements SchemaAccess
{
+ @Override
+ public String toString()
+ {
+ return "WRITER";
+ }
}
}
diff --git a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/DBSchemaTransaction.java b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/DBSchemaTransaction.java
index 0c584ec41f..acf371ea4b 100644
--- a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/DBSchemaTransaction.java
+++ b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/DBSchemaTransaction.java
@@ -107,10 +107,13 @@ public final class DBSchemaTransaction implements IDBSchemaTransaction, Runnable
public void close()
{
- database.closeSchemaTransaction();
- transaction = null;
- oldSchema = null;
- schema = null;
+ if (!isClosed())
+ {
+ database.closeSchemaTransaction();
+ transaction = null;
+ oldSchema = null;
+ schema = null;
+ }
}
public boolean isClosed()
diff --git a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/ddl/delta/DBDelta.java b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/ddl/delta/DBDelta.java
index ec00146c92..facd440dd3 100644
--- a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/ddl/delta/DBDelta.java
+++ b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/ddl/delta/DBDelta.java
@@ -13,6 +13,7 @@ package org.eclipse.net4j.internal.db.ddl.delta;
import org.eclipse.net4j.db.ddl.IDBSchemaElement;
import org.eclipse.net4j.db.ddl.delta.IDBDelta;
import org.eclipse.net4j.db.ddl.delta.IDBDeltaVisitor;
+import org.eclipse.net4j.db.ddl.delta.IDBDeltaVisitor.StopRecursion;
import org.eclipse.net4j.spi.db.DBNamedElement;
import org.eclipse.net4j.spi.db.DBSchemaElement;
import org.eclipse.net4j.util.StringUtil;
@@ -83,7 +84,15 @@ public abstract class DBDelta extends DBNamedElement implements IDBDelta
public final void accept(IDBDeltaVisitor visitor)
{
- doAccept(visitor);
+ try
+ {
+ doAccept(visitor);
+ }
+ catch (StopRecursion ex)
+ {
+ return;
+ }
+
for (IDBDelta delta : getElements())
{
delta.accept(visitor);
diff --git a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/ddl/delta/DBTableDelta.java b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/ddl/delta/DBTableDelta.java
index 1b7e1d0b8d..596ecc5158 100644
--- a/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/ddl/delta/DBTableDelta.java
+++ b/plugins/org.eclipse.net4j.db/src/org/eclipse/net4j/internal/db/ddl/delta/DBTableDelta.java
@@ -63,7 +63,7 @@ public final class DBTableDelta extends DBDelta implements IDBTableDelta
}
});
- IDBIndex[] indices = table.getIndices();
+ IDBIndex[] indices = table == null ? DBTable.NO_INDICES : table.getIndices();
IDBIndex[] oldIndices = oldTable == null ? DBTable.NO_INDICES : oldTable.getIndices();
compare(indices, oldIndices, new SchemaElementComparator<IDBIndex>()
{
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/Pair.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/Pair.java
index f22a62398c..9826988662 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/Pair.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/Pair.java
@@ -97,4 +97,28 @@ public class Pair<T1, T2>
{
return "Pair[" + element1 + ", " + element2 + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
+
+ /**
+ * @since 3.3
+ */
+ public Pair<T1, T2> copy()
+ {
+ return new Pair<T1, T2>(this);
+ }
+
+ /**
+ * @since 3.3
+ */
+ public static <T1, T2> Pair<T1, T2> create()
+ {
+ return new Pair<T1, T2>();
+ }
+
+ /**
+ * @since 3.3
+ */
+ public static <T1, T2> Pair<T1, T2> create(T1 element1, T2 element2)
+ {
+ return new Pair<T1, T2>(element1, element2);
+ }
}

Back to the top