00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 package fr.dyade.aaa.util;
00025
00026 import java.io.*;
00027 import java.util.*;
00028
00029 import org.objectweb.util.monolog.api.BasicLevel;
00030 import org.objectweb.util.monolog.api.Logger;
00031
00032 import fr.dyade.aaa.agent.Debug;
00033
00034 public class ATransaction implements Transaction, Runnable {
00035
00036 private int phase;
00037
00038 private static Logger logmon = null;
00039
00040 final static int CLEANUP_THRESHOLD_COMMIT = 9600;
00041 final static int CLEANUP_THRESHOLD_OPERATION = 36000;
00042 final static int CLEANUP_THRESHOLD_SIZE = 8 * Mb;
00043
00044 private int commitCount = 0;
00045 private int operationCount = 0;
00046 private int cumulativeSize = 0;
00047
00048 private class Context {
00049 Hashtable log = null;
00050 ByteArrayOutputStream bos = null;
00051 ObjectOutputStream oos = null;
00052
00053 Context() {
00054 log = new Hashtable(15);
00055 bos = new ByteArrayOutputStream(256);
00056 }
00057 }
00058
00059 static private final byte[] OOS_STREAM_HEADER = {
00060 (byte)((ObjectStreamConstants.STREAM_MAGIC >>> 8) & 0xFF),
00061 (byte)((ObjectStreamConstants.STREAM_MAGIC >>> 0) & 0xFF),
00062 (byte)((ObjectStreamConstants.STREAM_VERSION >>> 8) & 0xFF),
00063 (byte)((ObjectStreamConstants.STREAM_VERSION >>> 0) & 0xFF)
00064 };
00065
00072 private ThreadLocal perThreadContext = null;
00078 private Hashtable clog = null;
00083 private Hashtable plog = null;
00084
00085 private File dir = null;
00086
00087 static private final String LOCK = "lock";
00088 static private final String LOG = "log";
00089 static private final String PLOG = "plog";
00090 private File lockFile = null;
00091 protected File logFilePN = null;
00092 protected File plogFilePN = null;
00093
00094
00095 private boolean garbage;
00096 private Object lock = null;
00097 private boolean isRunning;
00098
00099 private Thread gThread = null;
00100
00101 static final boolean debug = false;
00102
00103 public ATransaction() {}
00104
00105 public boolean isPersistent() {
00106 return true;
00107 }
00108
00109 public final void init(String path) throws IOException {
00110 phase = INIT;
00111
00112 logmon = Debug.getLogger(Debug.A3Debug + ".Transaction");
00113 if (logmon.isLoggable(BasicLevel.INFO))
00114 logmon.log(BasicLevel.INFO, "ATransaction, init()");
00115
00116
00117
00118 dir = new File(path);
00119 if (!dir.exists()) dir.mkdir();
00120 if (!dir.isDirectory())
00121 throw new FileNotFoundException(path + " is not a directory.");
00122
00123
00124
00125 DataOutputStream ldos = null;
00126 try {
00127 lockFile = new File(dir, LOCK);
00128 if (! lockFile.createNewFile()) {
00129 logmon.log(BasicLevel.FATAL,
00130 "ATransaction.init(): " +
00131 "Either the server is already running, " +
00132 "either you have to remove lock file: " +
00133 lockFile.getAbsolutePath());
00134 throw new IOException("Transaction already running.");
00135 }
00136 lockFile.deleteOnExit();
00137 File tfc = new File(dir, "TFC");
00138 if (! tfc.exists()) {
00139 ldos = new DataOutputStream(new FileOutputStream(tfc));
00140 ldos.writeUTF(getClass().getName());
00141 ldos.flush();
00142 }
00143 } finally {
00144 if (ldos != null) ldos.close();
00145 }
00146
00147 logFilePN = new File(dir, LOG);
00148 plogFilePN = new File(dir, PLOG);
00149
00150 Hashtable tempLog = new Hashtable();
00151 restart(tempLog, logFilePN);
00152 restart(tempLog, plogFilePN);
00153 commit(tempLog);
00154
00155 plogFilePN.delete();
00156 logFilePN.delete();
00157
00158 perThreadContext = new ThreadLocal() {
00159 protected synchronized Object initialValue() {
00160 return new Context();
00161 }
00162 };
00163
00164 clog = new Hashtable(CLEANUP_THRESHOLD_OPERATION / 2);
00165 plog = new Hashtable(CLEANUP_THRESHOLD_OPERATION / 2);
00166
00167 baos = new ByteArrayOutputStream(10 * Kb);
00168 dos = new DataOutputStream(baos);
00169
00170 newLogFile();
00171
00172 lock = new Object();
00173 garbage = false;
00174 gThread = new Thread(this, "TGarbage");
00175 gThread.start();
00176
00177 if (logmon.isLoggable(BasicLevel.INFO))
00178 logmon.log(BasicLevel.INFO, "ATransaction, initialized");
00179
00180
00181 setPhase(FREE);
00182 }
00183
00184 private final void restart(Hashtable log, File logFilePN) throws IOException {
00185 if (logmon.isLoggable(BasicLevel.INFO))
00186 logmon.log(BasicLevel.INFO, "ATransaction, restart");
00187
00188 if ((logFilePN.exists()) && (logFilePN.length() > 0)) {
00189 RandomAccessFile logFile = new RandomAccessFile(logFilePN, "r");
00190 try {
00191 Hashtable templog = new Hashtable();
00192 while (true) {
00193 int optype;
00194 String dirName;
00195 String name;
00196 while ((optype = logFile.read()) != ATOperation.COMMIT) {
00197
00198
00199 dirName = logFile.readUTF();
00200 if (dirName.length() == 0) dirName = null;
00201 name = logFile.readUTF();
00202
00203 Object key = ATOperationKey.newKey(dirName, name);
00204
00205 ATOperation op = null;
00206 if (optype == ATOperation.SAVE) {
00207 byte buf[] = new byte[logFile.readInt()];
00208 logFile.readFully(buf);
00209 op = ATOperation.alloc(optype, dirName, name, buf);
00210 op = (ATOperation) templog.put(key, op);
00211 } else {
00212 op = ATOperation.alloc(optype, dirName, name);
00213 op = (ATOperation) templog.put(key, op);
00214 }
00215 if (op != null) op.free();
00216 }
00217
00218
00219 log.putAll(templog);
00220 templog.clear();
00221 }
00222 } catch (EOFException exc) {
00223 logFile.close();
00224 } catch (IOException exc) {
00225 logFile.close();
00226 throw exc;
00227 }
00228 }
00229 if (logmon.isLoggable(BasicLevel.INFO))
00230 logmon.log(BasicLevel.INFO, "ATransaction, started");
00231 }
00232
00233 public final File getDir() {
00234 return dir;
00235 }
00236
00237 private final void setPhase(int newPhase) {
00238 phase = newPhase;
00239 }
00240
00241 public int getPhase() {
00242 return phase;
00243 }
00244
00245 public String getPhaseInfo() {
00246 return PhaseInfo[phase];
00247 }
00248
00249 public final synchronized void begin() throws IOException {
00250 while (phase != FREE) {
00251 try {
00252 wait();
00253 } catch (InterruptedException exc) {
00254 }
00255 }
00256
00257 setPhase(RUN);
00258 }
00259
00260
00261 public final String[] getList(String prefix) {
00262 return dir.list(new StartWithFilter(prefix));
00263 }
00264
00265 public final void create(Serializable obj, String name) throws IOException {
00266 save(obj, null, name);
00267 }
00268
00269 public final void save(Serializable obj, String name) throws IOException {
00270 save(obj, null, name);
00271 }
00272
00273 public final void create(Serializable obj, String dirName, String name) throws IOException {
00274 save(obj, dirName, name);
00275 }
00276
00277 public final void save(Serializable obj,
00278 String dirName, String name) throws IOException {
00279 if (logmon.isLoggable(BasicLevel.DEBUG))
00280 logmon.log(BasicLevel.DEBUG, "ATransaction, save(" + dirName + ", " + name + ")");
00281
00282 Context ctx = (Context) perThreadContext.get();
00283 if (ctx.oos == null) {
00284 ctx.bos.reset();
00285 ctx.oos = new ObjectOutputStream(ctx.bos);
00286 } else {
00287 ctx.oos.reset();
00288 ctx.bos.reset();
00289 ctx.bos.write(OOS_STREAM_HEADER, 0, 4);
00290 }
00291 ctx.oos.writeObject(obj);
00292 ctx.oos.flush();
00293
00294 saveInLog(ctx.bos.toByteArray(), dirName, name, ctx.log, false);
00295 }
00296
00301 public final void saveByteArray(byte[] buf, String name) throws IOException {
00302 saveByteArray(buf, null, name);
00303 }
00304
00309 public final void saveByteArray(byte[] buf,
00310 String dirName, String name) throws IOException {
00311 saveInLog(buf,
00312 dirName, name,
00313 ((Context) perThreadContext.get()).log, true);
00314 }
00315
00316 private final void saveInLog(byte[] buf,
00317 String dirName, String name,
00318 Hashtable log,
00319 boolean copy) throws IOException {
00320 Object key = ATOperationKey.newKey(dirName, name);
00321 ATOperation op = ATOperation.alloc(ATOperation.SAVE, dirName, name, buf);
00322 ATOperation old = (ATOperation) log.put(key, op);
00323 if (copy) {
00324 if ((old != null) &&
00325 (old.type == ATOperation.SAVE) &&
00326 (old.value.length == buf.length)) {
00327
00328 op.value = old.value;
00329 } else {
00330
00331 op.value = new byte[buf.length];
00332 }
00333 System.arraycopy(buf, 0, op.value, 0, buf.length);
00334 }
00335 if (old != null) old.free();
00336
00337 }
00338
00339 private final byte[] getFromLog(Hashtable log, Object key) throws IOException {
00340
00341 ATOperation op = (ATOperation) log.get(key);
00342 if (op != null) {
00343 if (op.type == ATOperation.SAVE) {
00344 return op.value;
00345 } else if (op.type == ATOperation.DELETE) {
00346
00347 throw new FileNotFoundException();
00348 }
00349 }
00350 return null;
00351 }
00352
00353 private final byte[] getFromLog(String dirName, String name) throws IOException {
00354
00355 Object key = ATOperationKey.newKey(dirName, name);
00356 byte[] buf = getFromLog(((Context) perThreadContext.get()).log, key);
00357 if (buf != null) return buf;
00358
00359 if (((buf = getFromLog(clog, key)) != null) ||
00360 ((buf = getFromLog(plog, key)) != null)) {
00361 return buf;
00362 }
00363 return null;
00364 }
00365
00366 public final Object load(String name) throws IOException, ClassNotFoundException {
00367 return load(null, name);
00368 }
00369
00370 public final Object load(String dirName, String name) throws IOException, ClassNotFoundException {
00371 if (logmon.isLoggable(BasicLevel.DEBUG))
00372 logmon.log(BasicLevel.DEBUG, "ATransaction, load(" + dirName + ", " + name + ")");
00373
00374
00375 try {
00376 byte[] buf = getFromLog(dirName, name);
00377 if (buf != null) {
00378 ByteArrayInputStream bis = new ByteArrayInputStream(buf);
00379 ObjectInputStream ois = new ObjectInputStream(bis);
00380 return ois.readObject();
00381 }
00382
00383
00384 File file;
00385 Object obj;
00386 if (dirName == null) {
00387 file = new File(dir, name);
00388 } else {
00389 File parentDir = new File(dir, dirName);
00390 file = new File(parentDir, name);
00391 }
00392 FileInputStream fis = new FileInputStream(file);
00393 ObjectInputStream ois = new ObjectInputStream(fis);
00394 obj = ois.readObject();
00395
00396 fis.close();
00397 return obj;
00398 } catch (FileNotFoundException exc) {
00399 return null;
00400 }
00401 }
00402
00403 public final byte[] loadByteArray(String name) throws IOException {
00404 return loadByteArray(null, name);
00405 }
00406
00407 public final byte[] loadByteArray(String dirName, String name) throws IOException {
00408
00409 try {
00410 byte[] buf = getFromLog(dirName, name);
00411 if (buf != null) return buf;
00412
00413
00414 File file;
00415 if (dirName == null) {
00416 file = new File(dir, name);
00417 } else {
00418 File parentDir = new File(dir, dirName);
00419 file = new File(parentDir, name);
00420 }
00421 FileInputStream fis = new FileInputStream(file);
00422 buf = new byte[(int) file.length()];
00423 for (int nb=0; nb<buf.length; ) {
00424 int ret = fis.read(buf, nb, buf.length-nb);
00425 if (ret == -1) throw new EOFException();
00426 nb += ret;
00427 }
00428 fis.close();
00429
00430 return buf;
00431 } catch (FileNotFoundException exc) {
00432 return null;
00433 }
00434 }
00435
00436 public final void delete(String name) {
00437 delete(null, name);
00438 }
00439
00440 public final void delete(String dirName, String name) {
00441 Object key = ATOperationKey.newKey(dirName, name);
00442
00443 Hashtable log = ((Context) perThreadContext.get()).log;
00444 ATOperation op = ATOperation.alloc(ATOperation.DELETE, dirName, name);
00445 op = (ATOperation) log.put(key, op);
00446 if (op != null) op.free();
00447 }
00448
00449 static private final byte[] emptyUTFString = {0, 0};
00450
00451 static private ByteArrayOutputStream baos = null;
00452 static private DataOutputStream dos = null;
00453
00454 public void commit(boolean release) throws IOException {
00455 if (phase != RUN)
00456 throw new IllegalStateException("Can not commit.");
00457
00458 if (logmon.isLoggable(BasicLevel.DEBUG))
00459 logmon.log(BasicLevel.DEBUG, "ATransaction, commit");
00460
00461 commitCount += 1;
00462 Hashtable log = ((Context) perThreadContext.get()).log;
00463 if (! log.isEmpty()) {
00464 ATOperation op = null;
00465 for (Enumeration e = log.elements(); e.hasMoreElements(); ) {
00466 op = (ATOperation) e.nextElement();
00467
00468 operationCount += 1;
00469
00470 dos.write(op.type);
00471 if (op.dirName != null) {
00472 dos.writeUTF(op.dirName);
00473 } else {
00474 dos.write(emptyUTFString);
00475 }
00476 dos.writeUTF(op.name);
00477 if (op.type == ATOperation.SAVE) {
00478 dos.writeInt(op.value.length);
00479 dos.write(op.value);
00480 cumulativeSize += op.value.length;
00481 }
00482
00483
00484 op = (ATOperation) clog.put(ATOperationKey.newKey(op.dirName, op.name), op);
00485 if (op != null) op.free();
00486 }
00487 dos.writeByte(ATOperation.COMMIT);
00488 dos.flush();
00489 logFile.write(baos.toByteArray());
00490
00491 baos.reset();
00492
00493 syncLogFile();
00494
00495 log.clear();
00496 }
00497
00498 if (logmon.isLoggable(BasicLevel.DEBUG))
00499 logmon.log(BasicLevel.DEBUG, "ATransaction, committed");
00500
00501 setPhase(COMMIT);
00502
00503 if (release) {
00504 release();
00505 }
00506 }
00507
00508 protected RandomAccessFile logFile = null;
00509 protected FileDescriptor logFD = null;
00510
00511 protected void newLogFile() throws IOException {
00512 logFile = new RandomAccessFile(logFilePN, "rw");
00513 logFD = logFile.getFD();
00514 }
00515
00516 protected void syncLogFile() throws IOException {
00517 logFD.sync();
00518 }
00519
00520 public final synchronized void rollback() throws IOException {
00521 if (phase != RUN)
00522 throw new IllegalStateException("Can not rollback.");
00523
00524 if (logmon.isLoggable(BasicLevel.DEBUG))
00525 logmon.log(BasicLevel.DEBUG, "ATransaction, rollback");
00526
00527 setPhase(ROLLBACK);
00528 ((Context) perThreadContext.get()).log.clear();
00529 }
00530
00531 public final synchronized void release() throws IOException {
00532 if ((phase != RUN) && (phase != COMMIT) && (phase != ROLLBACK))
00533 throw new IllegalStateException("Can not release transaction.");
00534
00535 if (((commitCount > CLEANUP_THRESHOLD_COMMIT) ||
00536 (operationCount > CLEANUP_THRESHOLD_OPERATION) ||
00537 (cumulativeSize > CLEANUP_THRESHOLD_SIZE)) && !garbage) {
00538 synchronized (lock) {
00539
00540 garbage = true;
00541
00542 setPhase(GARBAGE);
00543 lock.notify();
00544 }
00545 } else {
00546 _release();
00547 }
00548 }
00549
00550 private final synchronized void _release() {
00551
00552 setPhase(FREE);
00553
00554 notify();
00555 }
00556
00560 private final void commit(Hashtable log) throws IOException {
00561 if (logmon.isLoggable(BasicLevel.DEBUG))
00562 logmon.log(BasicLevel.DEBUG,
00563 "ATransaction, Commit(" + log + ")");
00564
00565 ATOperation op = null;
00566 for (Enumeration e = log.elements(); e.hasMoreElements(); ) {
00567 op = (ATOperation) e.nextElement();
00568
00569 if (op.type == ATOperation.SAVE) {
00570 if (logmon.isLoggable(BasicLevel.DEBUG))
00571 logmon.log(BasicLevel.DEBUG,
00572 "ATransaction, Save (" + op.dirName + ',' + op.name + ')');
00573
00574 File file;
00575 if (op.dirName == null) {
00576 file = new File(dir, op.name);
00577 } else {
00578 File parentDir = new File(dir, op.dirName);
00579 if (!parentDir.exists()) {
00580 parentDir.mkdirs();
00581 }
00582 file = new File(parentDir, op.name);
00583 }
00584
00585 FileOutputStream fos = new FileOutputStream(file);
00586 fos.write(op.value);
00587 fos.getFD().sync();
00588 fos.close();
00589 } else if (op.type == ATOperation.DELETE) {
00590 if (logmon.isLoggable(BasicLevel.DEBUG))
00591 logmon.log(BasicLevel.DEBUG,
00592 "ATransaction, Delete (" + op.dirName + ',' + op.name + ')');
00593
00594 File file;
00595 boolean deleted;
00596 if (op.dirName == null) {
00597 file = new File(dir, op.name);
00598 deleted = file.delete();
00599 } else {
00600 File parentDir = new File(dir, op.dirName);
00601 file = new File(parentDir, op.name);
00602 deleted = file.delete();
00603 deleteDir(parentDir);
00604 }
00605
00606 if (!deleted && file.exists())
00607 logmon.log(BasicLevel.ERROR,
00608 "ATransaction, can't delete " + file.getCanonicalPath());
00609 }
00610 op.free();
00611 }
00612
00613
00614 log.clear();
00615
00616 if (logmon.isLoggable(BasicLevel.DEBUG))
00617 logmon.log(BasicLevel.DEBUG, "ATransaction, Committed");
00618
00619 }
00620
00626 private final void deleteDir(File dir) {
00627
00628
00629
00630
00631 String[] children = dir.list();
00632
00633 if (children != null && children.length == 0) {
00634 dir.delete();
00635 if (dir.getAbsolutePath().length() >
00636 this.dir.getAbsolutePath().length()) {
00637 deleteDir(dir.getParentFile());
00638 }
00639 }
00640 }
00641
00642 public final synchronized void _stop() {
00643 synchronized (lock) {
00644 while (phase != FREE) {
00645
00646 try {
00647 wait();
00648 } catch (InterruptedException exc) {
00649 }
00650 }
00651
00652 setPhase(FINALIZE);
00653 isRunning = false;
00654 garbage = true;
00655
00656 lock.notify();
00657 }
00658 }
00659
00660 public final void stop() {
00661 if (logmon.isLoggable(BasicLevel.INFO))
00662 logmon.log(BasicLevel.INFO, "ATransaction, stops");
00663
00664 _stop();
00665
00666 try {
00667
00668 gThread.join();
00669 } catch (InterruptedException exc3) {
00670 if (logmon.isLoggable(BasicLevel.WARN))
00671 logmon.log(BasicLevel.WARN, "ATransaction, interrupted");
00672 }
00673
00674 if (logmon.isLoggable(BasicLevel.INFO))
00675 logmon.log(BasicLevel.INFO, "ATransaction, stopped");
00676 }
00677
00683 public void close() {
00684 stop();
00685 }
00686
00687 public void run() {
00688 if (isRunning) return;
00689 isRunning = true;
00690
00691 try {
00692
00693
00694
00695
00696
00697 while (isRunning || garbage) {
00698 synchronized (lock) {
00699 while (! garbage) {
00700 try {
00701 lock.wait();
00702 } catch (InterruptedException exc) {}
00703 }
00704 garbage = false;
00705 }
00706
00707 wakeup();
00708 }
00709 } catch (IOException exc) {
00710 if (logmon.isLoggable(BasicLevel.ERROR))
00711 logmon.log(BasicLevel.ERROR, "ATransaction, tgarbage", exc);
00712
00713 exc.printStackTrace();
00714 } finally {
00715 isRunning = false;
00716
00717 if (logmon.isLoggable(BasicLevel.DEBUG))
00718 logmon.log(BasicLevel.DEBUG, "ATransaction, ends");
00719
00720 try {
00721 logFile.close();
00722 } catch (IOException exc) {
00723 logmon.log(BasicLevel.WARN, "ATransaction, can't close logfile", exc);
00724 }
00725
00726 if (! lockFile.delete()) {
00727 logmon.log(BasicLevel.FATAL,
00728 "ATransaction, - can't delete lockfile.");
00729 }
00730
00731 if (logmon.isLoggable(BasicLevel.INFO))
00732 logmon.log(BasicLevel.INFO, "ATransaction, exits.");
00733 }
00734 }
00735
00736 private final void wakeup() throws IOException {
00737 if (logmon.isLoggable(BasicLevel.INFO))
00738 logmon.log(BasicLevel.INFO,
00739 "ATransaction, Wakeup: " + commitCount + ", " +
00740 operationCount + ", " + cumulativeSize);
00741 commitCount = operationCount = cumulativeSize = 0;
00742
00743 Hashtable templog = plog;
00744 plog = clog;
00745 clog = templog;
00746
00747 logFile.close();
00748
00749 logFilePN.renameTo(plogFilePN);
00750 newLogFile();
00751
00752 _release();
00753
00754 commit(plog);
00755
00756 plogFilePN.delete();
00757
00758 if (logmon.isLoggable(BasicLevel.INFO))
00759 logmon.log(BasicLevel.INFO, "ATransaction, Wakeup: end");
00760 }
00761 }
00762
00763 final class ATOperation implements Serializable {
00764 static final int SAVE = 1;
00765 static final int DELETE = 2;
00766 static final int COMMIT = 3;
00767 static final int END = 127;
00768
00769 int type;
00770 String dirName;
00771 String name;
00772 byte[] value;
00773
00774 private ATOperation(int type, String dirName, String name, byte[] value) {
00775 this.type = type;
00776 this.dirName = dirName;
00777 this.name = name;
00778 this.value = value;
00779 }
00780
00786 public String toString() {
00787 StringBuffer strbuf = new StringBuffer();
00788
00789 strbuf.append('(').append(super.toString());
00790 strbuf.append(",type=").append(type);
00791 strbuf.append(",dirName=").append(dirName);
00792 strbuf.append(",name=").append(name);
00793 strbuf.append(')');
00794
00795 return strbuf.toString();
00796 }
00797
00798 private static Pool pool = null;
00799
00800 static {
00801 pool = new Pool("Atransaction$ATOperation",
00802 ATransaction.CLEANUP_THRESHOLD_OPERATION);
00803 }
00804
00805 static ATOperation alloc(int type, String dirName, String name) {
00806 return alloc(type, dirName, name, null);
00807 }
00808
00809 static ATOperation alloc(int type,
00810 String dirName, String name,
00811 byte[] value) {
00812 ATOperation op = null;
00813
00814 try {
00815 op = (ATOperation) pool.allocElement();
00816 } catch (Exception exc) {
00817 return new ATOperation(type, dirName, name, value);
00818 }
00819 op.type = type;
00820 op.dirName = dirName;
00821 op.name = name;
00822 op.value = value;
00823 return op;
00824 }
00825
00826 void free() {
00827
00828 dirName = null;
00829 name = null;
00830 value = null;
00831 pool.freeElement(this);
00832 }
00833 }
00834
00835 final class ATOperationKey {
00836 static Object newKey(String dirName, String name) {
00837 if (dirName == null) {
00838 return name;
00839 } else {
00840 return new ATOperationKey(dirName, name);
00841 }
00842 }
00843
00844 private String dirName;
00845 private String name;
00846
00847 private ATOperationKey(String dirName,
00848 String name) {
00849 this.dirName = dirName;
00850 this.name = name;
00851 }
00852
00853 public int hashCode() {
00854
00855 return dirName.hashCode();
00856 }
00857
00858 public boolean equals(Object obj) {
00859 if (this == obj) return true;
00860 if (obj instanceof ATOperationKey) {
00861 ATOperationKey opk = (ATOperationKey)obj;
00862 if (opk.name.length() != name.length()) return false;
00863 if (opk.dirName.length() != dirName.length()) return false;
00864 if (!opk.dirName.equals(dirName)) return false;
00865 return opk.name.equals(name);
00866 }
00867 return false;
00868 }
00869 }