001: /**
002: * generated by RDFReactor on 9:59 on 29.2005
003: */package org.ontoware.semversion;
004:
005: import java.util.ArrayList;
006: import java.util.Calendar;
007: import java.util.List;
008:
009: import org.ontoware.aifbcommons.collection.ClosableIterator;
010: import org.ontoware.rdf2go.model.Diff;
011: import org.ontoware.rdf2go.model.Model;
012: import org.ontoware.rdf2go.model.Statement;
013: import org.ontoware.rdf2go.model.node.URI;
014: import org.ontoware.rdfreactor.runtime.Bridge;
015: import org.ontoware.rdfreactor.runtime.RDFDataException;
016: import org.ontoware.semversion.impl.BlankNodeEnrichmentModel;
017: import org.ontoware.semversion.impl.SyntacticDiffEngine;
018: import org.ontoware.semversion.impl.generated.RDFModel;
019:
020: /**
021: * A SemVersion Version. Each version is a non-mutable state of an RDF model. A
022: * version has a number of metadata attached to it. Each version is identified
023: * by a URI. A {@link VersionedModel} has none or one root version. Each version
024: * can have a number of chuld versions. Versions can have branch labels. The
025: * default branch is called 'main'.
026: *
027: * Each model can have a number of 'suggested' children. Suggestions can have
028: * further children, but these must also be suggestions. <code><pre>
029: * Example:
030: *
031: * root
032: * - version 1
033: * - version 2
034: * - version 2.1 (suggestion)
035: * - version 2.1.1 (must be a suggestion)
036: * - version 2.2
037: * - version 2.3
038: * </pre></code>
039: *
040: * @author voelkel
041: */
042: public class Version extends VersionedItem {
043:
044: private org.ontoware.semversion.impl.generated.Version version;
045:
046: /**
047: * SHOULD NOT BE CALLED FROM API USERS
048: *
049: * @param model
050: * @param uri
051: * @param write
052: */
053: public Version(Model model, Session session, URI uri, boolean write) {
054: super (model, session, uri);
055: this .version = new org.ontoware.semversion.impl.generated.Version(
056: model, uri, write);
057: }
058:
059: /**
060: * SHOULD NOT BE CALLED FROM API USERS
061: *
062: * @param version
063: */
064: public Version(
065: org.ontoware.semversion.impl.generated.Version version,
066: Session session) {
067: super (version.getModel(), session, version.getResource()
068: .asURI());
069: this .version = version;
070: }
071:
072: private boolean branchLabelExists(String branchLabel) {
073: try {
074: for (Version v : this .getVersionedModel().getAllVersions()) {
075: if (v.getBranchLabel().equals(branchLabel))
076: return true;
077: }
078: } catch (RDFDataException e) {
079: throw new RuntimeException(e);
080: }
081: return false;
082: }
083:
084: /**
085: * @param diff
086: * @param comment
087: * @param versionURI
088: * the contextURI of the new model
089: * @param provenance
090: * @param suggestion
091: * if true, the the version is a suggestion
092: * @return
093: * @throws CommitConflictException
094: */
095: public Version commit(Diff diff, String comment, URI versionURI,
096: URI provenance, boolean suggestion)
097: throws CommitConflictException {
098: if (!suggestion && isSuggestion())
099: throw new InvalidChildOfSuggestionException();
100: checkForCommitConflicts(suggestion);
101: // apply diff
102: Model content = getContent();
103: Model temp = SyntacticDiffEngine.applyDiff(getSemVersion()
104: .getTripleStore(), content, diff);
105: content.close();
106: return commit(temp, comment, versionURI, provenance, suggestion);
107: }
108:
109: /**
110: * @param suggestion
111: * true if someone wants to commit a suggestion
112: * @return
113: */
114: private void checkForCommitConflicts(boolean suggestion) {
115: if (!suggestion && this .hasValidChildren()) {
116: throw new CommitConflictException();
117: }
118: }
119:
120: /**
121: * Create a new child version by committing explicitly the content of the
122: * child
123: *
124: * @param childContent
125: * @param comment
126: * @param suggestion
127: * if true, the new version has not been accepted yet, it is just
128: * a suggestion
129: * @return the child version of this version that has the content
130: * 'childContent'
131: * @throws CommitConflictException
132: * @throws InvalidChildOfSuggestionException
133: */
134: public Version commit(Model childContent, String comment,
135: boolean suggestion) throws CommitConflictException,
136: InvalidChildOfSuggestionException {
137: if (!suggestion && isSuggestion())
138: throw new InvalidChildOfSuggestionException();
139: checkForCommitConflicts(suggestion);
140: return commit(childContent, comment, getSemVersion()
141: .getTripleStore().newRandomUniqueURI(), null,
142: suggestion);
143: }
144:
145: /**
146: * Create a new child version by committing explicitly the content of the
147: * child. The new version has the given URI.
148: *
149: * @param childContent
150: * @param comment
151: * @param versionURI
152: * @param provenance
153: * @param suggestion
154: * @return
155: */
156: public Version commit(Model childContent, String comment,
157: URI versionURI, URI provenance, boolean suggestion) {
158: // Impl: commits a suggestion, setValid then creates a 'real' version
159: if (!suggestion && isSuggestion())
160: throw new InvalidChildOfSuggestionException();
161: checkForCommitConflicts(suggestion);
162:
163: Version child = new Version(getSemVersion().getMainModel(),
164: getSession(), versionURI, true);
165: // add meta-data
166: try {
167: // store model
168: Model childModel = getSemVersion().getTripleStore()
169: .addModelAndPersist(childContent);
170: childContent.close();
171: childModel.close();
172:
173: child.setUser(getSession().getUser());
174: child.setProvenance(provenance);
175: child.setCreationTime(Calendar.getInstance());
176:
177: child.setChangeCause("commit");
178:
179: child.setComment(comment);
180: child.setContainer(getVersionedModel());
181: if (!suggestion)
182: child.setValid();
183:
184: // carry over branch label
185: child.setBranchLabel(this .getBranchLabel());
186:
187: // link
188: RDFModel childContentModel = new RDFModel(getSemVersion()
189: .getMainModel(), childModel.getContextURI(), true);
190: child.setContent(childContentModel);
191: this .version.addChild(child.version);
192: child.setFirstParent(this );
193: // add to root
194: getVersionedModel().addVersion(child);
195: return child;
196: } catch (RDFDataException e) {
197: throw new RuntimeException(e);
198: } catch (Exception e) {
199: throw new RuntimeException(e);
200: }
201: }
202:
203: /**
204: * Commit a new version by applying a diff to this versions content. The new
205: * version lives in a new branch, named 'branchLabel'
206: *
207: * @param diff
208: * @param branchLabel
209: * may not be null
210: * @param comment
211: * @param versionURI
212: * may not be null
213: * @param provenance
214: * may be null
215: * @param suggestion
216: * @return
217: * @throws BranchlabelAlreadyUsedException
218: * @throws InvalidChildOfSuggestionException
219: */
220: public Version commitAsBranch(Diff diff, String branchLabel,
221: String comment, URI versionURI, URI provenance,
222: boolean suggestion) throws BranchlabelAlreadyUsedException,
223: InvalidChildOfSuggestionException {
224: if (versionURI == null)
225: throw new IllegalArgumentException("URI may not be null");
226: if (!suggestion && isSuggestion())
227: throw new InvalidChildOfSuggestionException();
228: if (branchLabelExists(branchLabel))
229: throw new BranchlabelAlreadyUsedException();
230: Version childVersion = commit(diff, comment, versionURI,
231: provenance, suggestion);
232: childVersion.setBranchLabel(branchLabel);
233: return childVersion;
234: }
235:
236: /**
237: * Create a new child version in a different branch by explicitly committing
238: * 'childContent' as the new content
239: *
240: * @param childContent
241: * @param branchLabel
242: * @param comment
243: * @param suggestion
244: * @return
245: * @throws BranchlabelAlreadyUsedException
246: * @throws InvalidChildOfSuggestionException
247: */
248: public Version commitAsBranch(Model childContent,
249: String branchLabel, String comment, boolean suggestion)
250: throws BranchlabelAlreadyUsedException,
251: InvalidChildOfSuggestionException {
252: if (!suggestion && isSuggestion())
253: throw new InvalidChildOfSuggestionException();
254: if (branchLabelExists(branchLabel))
255: throw new BranchlabelAlreadyUsedException();
256: Version childVersion = commit(childContent, comment, suggestion);
257: childVersion.setBranchLabel(branchLabel);
258: return childVersion;
259: }
260:
261: /**
262: * Creates a new child version with the given URI.
263: *
264: * @param childContent
265: * @param branchLabel
266: * @param comment
267: * @param versionURI
268: * @param provenance
269: * @param suggestion
270: * @return
271: * @throws BranchlabelAlreadyUsedException
272: * @throws InvalidChildOfSuggestionException
273: */
274: public Version commitAsBranch(Model childContent,
275: String branchLabel, String comment, URI versionURI,
276: URI provenance, boolean suggestion)
277: throws BranchlabelAlreadyUsedException,
278: InvalidChildOfSuggestionException {
279: if (versionURI == null)
280: throw new IllegalArgumentException("URI may not be null");
281: if (!suggestion && isSuggestion())
282: throw new InvalidChildOfSuggestionException();
283: if (branchLabelExists(branchLabel))
284: throw new BranchlabelAlreadyUsedException();
285: Version childVersion = commit(childContent, comment,
286: versionURI, provenance, suggestion);
287: childVersion.setBranchLabel(branchLabel);
288: return childVersion;
289: }
290:
291: /**
292: * @return a String which contains all content of this model in a
293: * self-invented strange format.
294: */
295: public String dump() {
296: StringBuffer buf = new StringBuffer();
297: try {
298: Model content = getContent();
299: ClosableIterator<Statement> iter = content.iterator();
300: while (iter.hasNext()) {
301: Statement s = iter.next();
302: buf.append("\t" + s.getSubject() + "\t"
303: + s.getPredicate() + "\t" + s.getObject()
304: + "\n");
305: }
306: iter.close();
307: content.close();
308: return buf.toString();
309: } catch (Exception e) {
310: throw new RuntimeException(e);
311: }
312: }
313:
314: public boolean equals(Object other) {
315: return ((other instanceof Version) && this .version
316: .getResource().equals(
317: ((Version) other).version.getResource()));
318: }
319:
320: // //////////////////////////////////////////////////
321: // versioning specific methods
322:
323: /**
324: * @return all child versions (suggestions and accepted) of this version
325: */
326: public List<Version> getAllChildren() {
327: org.ontoware.semversion.impl.generated.Version[] genChildVersions = (org.ontoware.semversion.impl.generated.Version[]) Bridge
328: .getAllValues(
329: version.getModel(),
330: version.getResource(),
331: org.ontoware.semversion.impl.generated.Version.CHILD,
332: org.ontoware.semversion.impl.generated.Version.class);
333:
334: List<Version> result = new ArrayList<Version>(
335: genChildVersions.length);
336: for (org.ontoware.semversion.impl.generated.Version genChild : genChildVersions) {
337: result.add(new Version(genChild, getSession()));
338: }
339: return result;
340: }
341:
342: /**
343: * @return the branch label of this version
344: */
345: public String getBranchLabel() {
346: return this .version.getBranchLabel();
347: }
348:
349: /**
350: * @return the change cause
351: */
352: public String getChangeCause() {
353: return this .version.getChangeCause();
354: }
355:
356: /**
357: * @return a an in-memory copy of the RDF content of this model
358: */
359: public Model getContent() {
360: return new BlankNodeEnrichmentModel(getSemVersion()
361: .getTripleStore().getAsTempCopy(getContentURI()));
362: }
363:
364: /**
365: * @return the URI of the named graph which is used to store this versions
366: * RDF model
367: */
368: private URI getContentURI() {
369: return version.getContent().getResource().asURI();
370: }
371:
372: /**
373: * @return predecessor, which is always a Version
374: */
375: protected Version getFirstParent() {
376: org.ontoware.semversion.impl.generated.Version reactorVersion = version
377: .getFirstParent();
378: if (reactorVersion == null)
379: return null;
380: else
381: return new Version(reactorVersion, getSession());
382: }
383:
384: /**
385: * @return all accepted child versions (including suggestions)
386: */
387: public List<Version> getNextVersions() {
388: List<Version> result = new ArrayList<Version>();
389: for (Version v : getAllChildren()) {
390: result.add(v);
391: }
392: return result;
393: }
394:
395: /**
396: * @return the first parent
397: */
398: public Version getPrevVersion() {
399: return getFirstParent();
400: }
401:
402: protected org.ontoware.semversion.impl.generated.Version getReactorVersion() {
403: return this .version;
404: }
405:
406: /**
407: * @return the second parent (a Version) if this version has been the result
408: * of a merge. Return null otherwise.
409: */
410: public Version getSecondParent() {
411: org.ontoware.semversion.impl.generated.Version secondParent = this .version
412: .getSecondParent();
413:
414: if (secondParent != null
415: && org.ontoware.semversion.impl.generated.Version
416: .hasInstance(version.getModel(), secondParent
417: .getResource().asURI())) {
418: return new Version(secondParent, getSession());
419: } else
420: return null;
421: }
422:
423: /**
424: * @return the size of the versioned RDF graph of this version
425: */
426: public long getStatementCount() {
427: Model m = getSemVersion().getTripleStore().getPersistentModel(
428: getContentURI());
429: long size = m.size();
430: m.close();
431: return size;
432: }
433:
434: /**
435: * @return all suggestions to this version
436: */
437: public List<Version> getSuggestions() {
438: List<Version> suggestions = new ArrayList<Version>();
439: for (Version child : getAllChildren()) {
440: if (!child.isValid())
441: suggestions.add(child);
442: }
443: return suggestions;
444: }
445:
446: /**
447: * @return all valid children of this version (i.e. children that are not a
448: * suggestion)
449: */
450: public List<Version> getValidChildren() {
451: List<Version> result = new ArrayList<Version>();
452: for (Version v : getAllChildren()) {
453: if (v.isValid())
454: result.add(v);
455: }
456: return result;
457: }
458:
459: /**
460: * @return the {@link VersionedModel} in which this version lives.
461: */
462: public VersionedModel getVersionedModel() throws RDFDataException {
463: return new VersionedModel(this .version.getContainer(),
464: getSession());
465: }
466:
467: protected boolean hasChildWithSameBranchLabel() {
468: List<Version> validChildren = getValidChildren();
469: for (Version v : validChildren) {
470: if (v.getBranchLabel().equals(getBranchLabel()))
471: return true;
472: }
473: return false;
474: }
475:
476: /**
477: * @return true if this version has at least one valid child version
478: */
479: public boolean hasValidChildren() {
480: return getValidChildren().size() > 0;
481: }
482:
483: /**
484: * @param other
485: * @return true if this version has the same branch label as the other
486: * version
487: */
488: public boolean isInSameBranch(Version other) {
489: return getBranchLabel().equals(other.getBranchLabel());
490: }
491:
492: /**
493: * @return true if this version is just a suggested version that has not
494: * been accepted yet
495: */
496: public boolean isSuggestion() {
497: return !isValid();
498: }
499:
500: /**
501: * @return true if this version is an accepted version (i.e. not a
502: * suggestion)
503: */
504: public boolean isValid() {
505: try {
506: return getTag() != null
507: && getTag().equals(SemVersion.VALID);
508: } catch (RDFDataException e) {
509: throw new RuntimeException(e);
510: }
511: }
512:
513: /**
514: * @param otherVersion
515: * @param comment
516: * @param provenance
517: * @param suggestion
518: * @return a new version which is the result of the merge (union) of two
519: * other versions (this version and 'other' version)
520: * @throws CommitConflictException
521: * @throws InvalidChildOfSuggestionException
522: */
523: public Version merge(Version otherVersion, String comment,
524: URI provenance, boolean suggestion)
525: throws CommitConflictException,
526: InvalidChildOfSuggestionException {
527: if (!suggestion && isSuggestion())
528: throw new InvalidChildOfSuggestionException();
529: checkForCommitConflicts(suggestion);
530: try {
531: URI childURI = getSemVersion().getMainModel()
532: .newRandomUniqueURI();
533: Version child = new Version(getSemVersion().getMainModel(),
534: getSession(), childURI, true);
535:
536: // set metadata
537:
538: child.setUser(getSession().getUser());
539: child.setProvenance(provenance);
540: child.setCreationTime(Calendar.getInstance());
541:
542: // link merge data
543: child.setFirstParent(this );
544: child.setSecondParent(otherVersion);
545:
546: child.setChangeCause("merge");
547: this .version.addChild(child.version);
548:
549: Model childModel = getSemVersion().getTripleStore()
550: .getAsTempCopy(getContentURI());
551: // add otherVersion
552: Model content = otherVersion.getContent();
553: ClosableIterator<Statement> it = content.iterator();
554: childModel.addAll(it);
555: it.close();
556: content.close();
557:
558: RDFModel childContent = new RDFModel(getSemVersion()
559: .getMainModel(), childModel.getContextURI(), true);
560: child.setContent(childContent);
561: child.setContainer(getVersionedModel());
562:
563: child.setComment(comment);
564:
565: if (!suggestion)
566: child.setValid();
567:
568: getVersionedModel().addVersion(child);
569: return child;
570:
571: } catch (Exception e) {
572: throw new RuntimeException(e);
573: }
574: }
575:
576: /**
577: * Changes the flag from "suggestion" to "released"
578: */
579: public void setAsRelease() {
580: // check that this has no suggestions as parents
581: if (getPrevVersion().isSuggestion()) {
582: throw new ConsistencyException("This version ("
583: + this .getURI() + ") has a suggestion-parent ("
584: + getPrevVersion().getURI()
585: + ") therefore this version cannot be released.");
586: }
587: // else
588: setValid();
589: }
590:
591: /**
592: * Changes the flag from "released" to "suggestion"
593: */
594: public void setAsSuggestion() {
595: // check that there are no released children
596: for (Version child : getNextVersions()) {
597: if (!child.isSuggestion()) {
598: throw new ConsistencyException(
599: "Cannot set this version ("
600: + getURI()
601: + ") as a suggestion because child versions are already released, e.g. "
602: + child.getURI());
603: }
604: }
605: setInvalid();
606: }
607:
608: private void setBranchLabel(String branchLabel) {
609: version.setBranchLabel(branchLabel);
610: }
611:
612: protected void setChangeCause(String value) {
613: this .version.setChangeCause(value);
614: }
615:
616: protected void setContainer(VersionedModel value) {
617: version.setContainer(value.getReactorVersionedModel());
618: }
619:
620: protected void setContent(RDFModel rdfmodel)
621: throws RDFDataException {
622: version.setContent(rdfmodel);
623: }
624:
625: protected void setFirstParent(Version value)
626: throws RDFDataException {
627: version.setFirstParent(value.getReactorVersion());
628: }
629:
630: /**
631: * Sets this version as invalid (= suggestion)
632: */
633: protected void setInvalid() {
634: org.ontoware.semversion.impl.generated.Version.removeAllTag(
635: this .version.getModel(), this .version.asResource());
636: }
637:
638: private void setSecondParent(Version value) throws RDFDataException {
639: version.setSecondParent(value.getReactorVersion());
640: }
641:
642: protected void setValid() {
643: try {
644: setTag(SemVersion.VALID);
645:
646: // inherit branch label from parent
647: if (this .getFirstParent() != null)
648: this .setBranchLabel(this .getFirstParent()
649: .getBranchLabel());
650: else
651: // first version must be main branch
652: this .setBranchLabel(SemVersion.MAIN_BRANCH);
653:
654: } catch (RDFDataException e) {
655: throw new RuntimeException(e);
656: }
657: }
658:
659: public void delete() {
660: org.ontoware.semversion.impl.generated.Version
661: .deleteAllProperties(version.getModel(), version
662: .asResource());
663: }
664:
665: }
|