001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019
020package org.apache.isis.core.metamodel.adapter.version;
021
022import java.io.IOException;
023import java.io.Serializable;
024import java.util.Date;
025
026import org.apache.isis.core.commons.encoding.DataInputExtended;
027import org.apache.isis.core.commons.encoding.DataOutputExtended;
028import org.apache.isis.core.commons.encoding.Encodable;
029import org.apache.isis.core.commons.lang.DateExtensions;
030import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
031import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
032
033/**
034 * An instance of this class is held by each {@link ObjectAdapter} and is used
035 * to represent a particular version (at a point in time) of domain object
036 * wrapped by that adapter.
037 * 
038 * <p>
039 * This is normally done using some form of incrementing number or timestamp,
040 * which would be held within the implementing class. The numbers, timestamps,
041 * etc should change for each changed object, and the different() method should
042 * indicate that the two Version objects are different.
043 * 
044 * <p>
045 * The user's name and a timestamp should alos be kept so that when an message
046 * is passed to the user it can be of the form "user has change object at time"
047 */
048public class Version implements Serializable, Encodable {
049
050    
051    private static final long serialVersionUID = 1L;
052    
053    private final Long sequence;
054    private final String user;
055    private final Long utcTimestamp;
056
057    // ///////////////////////////////////////////////////////
058    // factory methods
059    // ///////////////////////////////////////////////////////
060
061
062    public static Version create(final Long sequence) {
063        return create(sequence, null, (Long)null);
064    }
065
066    public static Version create(String sequence, String user, String utcTimestamp) {
067        if(sequence == null) { 
068            return null;
069        }
070        return create(Long.parseLong(sequence), user, utcTimestamp != null?Long.parseLong(utcTimestamp):null);
071    }
072
073    public static Version create(final Long sequence, final String user, final Date time) {
074        return create(sequence, user, time !=null? time.getTime(): null);
075    }
076
077    public static Version create(Long sequence, String user, Long utcTimestamp) {
078        if(sequence == null) { 
079            return null;
080        }
081        return new Version(sequence, user, utcTimestamp);
082    }
083
084    private Version(Long sequence, String user, Long utcTimestamp) {
085        this.sequence = sequence;
086        this.user = user;
087        this.utcTimestamp = utcTimestamp;
088    }
089
090    
091    // ///////////////////////////////////////////////////////
092    // encodable
093    // ///////////////////////////////////////////////////////
094
095    public Version(final DataInputExtended input) throws IOException {
096        this(input.readLong(), input.readUTF(), input.readLong());
097    }
098    
099    @Override
100    public void encode(final DataOutputExtended output) throws IOException {
101        output.writeLong(sequence);
102        output.writeUTF(user);
103        output.writeLong(utcTimestamp);
104    }
105
106
107    // ///////////////////////////////////////////////////////
108    // getters
109    // ///////////////////////////////////////////////////////
110
111    /**
112     * The internal, strictly monotonically increasing, version number.
113     * 
114     * <p>
115     * This might be the timestamp of the change, or it might be simply a number incrementing 1,2,3...
116     */
117    public long getSequence() {
118        return sequence;
119    }
120    
121    /**
122     * Returns the user who made the last change (used for display/reporting only)
123     * 
124     * <p>
125     * May be null.
126     */
127    public String getUser() {
128        return user;
129    }
130    
131    /**
132     * The time of the last change, as UTC milliseconds.
133     * 
134     * <p>
135     * May be null.
136     * 
137     * @see #getTime()
138     */
139    public Long getUtcTimestamp() {
140        return utcTimestamp;
141    }
142
143    /**
144     * Returns the time of the last change (used for display/reporting only, not comparison)
145     * 
146     * <p>
147     * May be null.
148     * 
149     * @see #getUtcTimestamp()
150     */
151    public Date getTime() {
152        return utcTimestamp != null? new Date(this.utcTimestamp): null;
153    }
154
155
156    // ///////////////////////////////////////////////////////
157    // enString
158    // ///////////////////////////////////////////////////////
159
160    public String enString(OidMarshaller oidMarshaller) {
161        return oidMarshaller.marshal(this);
162    }
163
164    // ///////////////////////////////////////////////////////
165    // equals, hashCode
166    // ///////////////////////////////////////////////////////
167
168    @Override
169    public int hashCode() {
170        final int prime = 31;
171        int result = 1;
172        result = prime * result + ((sequence == null) ? 0 : sequence.hashCode());
173        return result;
174    }
175
176    @Override
177    public boolean equals(Object obj) {
178        if (this == obj)
179            return true;
180        if (obj == null)
181            return false;
182        if (getClass() != obj.getClass())
183            return false;
184        Version other = (Version) obj;
185        if (sequence == null) {
186            if (other.sequence != null)
187                return false;
188        } else if (!sequence.equals(other.sequence))
189            return false;
190        return true;
191    }
192
193    /**
194     * Compares this version against the specified version and returns true if
195     * they are different versions (by checking {@link #getSequence()}).
196     * 
197     * <p>
198     * This is use for optimistic checking, where the existence of a different
199     * version will normally cause a concurrency exception.
200     */
201    public boolean different(Version version) {
202        return !equals(version);
203    }
204    
205    
206    //////////////////////////////////////////////////////////////
207    // sequence
208    //////////////////////////////////////////////////////////////
209    
210 
211    @Override
212    public String toString() {
213        return "#" + sequence + " " + getUser() + " " + DateExtensions.asTimestamp(getTime());
214    }
215    
216    /**
217     * Returns the sequence for printing/display
218     */
219    public String sequence() {
220        return Long.toString(sequence, 16);
221    }
222    
223}