-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds a new package with functionality to compute the delta of gbfs files
- Loading branch information
Showing
8 changed files
with
482 additions
and
0 deletions.
There are no files selected for viewing
184 changes: 184 additions & 0 deletions
184
src/main/java/org/entur/lamassu/delta/BaseGBFSFileDeltaCalculator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
/* | ||
* | ||
* | ||
* * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by | ||
* * the European Commission - subsequent versions of the EUPL (the "Licence"); | ||
* * You may not use this work except in compliance with the Licence. | ||
* * You may obtain a copy of the Licence at: | ||
* * | ||
* * https://joinup.ec.europa.eu/software/page/eupl | ||
* * | ||
* * Unless required by applicable law or agreed to in writing, software | ||
* * distributed under the Licence is distributed on an "AS IS" basis, | ||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* * See the Licence for the specific language governing permissions and | ||
* * limitations under the Licence. | ||
* | ||
*/ | ||
|
||
package org.entur.lamassu.delta; | ||
|
||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.lang.reflect.InvocationTargetException; | ||
import java.lang.reflect.Method; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
public abstract class BaseGBFSFileDeltaCalculator<S, T> implements GBFSFileDeltaCalculator<S, T> { | ||
private static final List<String> EXCLUDE_METHODS = List.of( | ||
"toString", | ||
"hashCode", | ||
"equals" | ||
); | ||
|
||
@Override | ||
public final GBFSFileDelta<T> calculateDelta(S base, @NotNull S compare) { | ||
List<GBFSEntityDelta<T>> entityDeltas = getEntityDeltas(base, compare); | ||
return getGBFSFileDelta(base, compare, entityDeltas); | ||
} | ||
|
||
private @NotNull GBFSFileDelta<T> getGBFSFileDelta(S base, @NotNull S compare, List<GBFSEntityDelta<T>> entityDeltas) { | ||
return new GBFSFileDelta<>( | ||
getLastUpdated(base), | ||
getLastUpdated(compare), | ||
getFileName(), | ||
entityDeltas | ||
); | ||
} | ||
|
||
private @NotNull List<GBFSEntityDelta<T>> getEntityDeltas(S base, @NotNull S compare) { | ||
List<T> baseEntities = getBaseEntities(base); | ||
Map<String, T> baseEntityMap = getBaseEntityMap(baseEntities); | ||
List<String> baseEntityIds = getEntityIds(baseEntityMap); | ||
List<T> compareEntities = getEntities(compare); | ||
List<String> compareEntityIds = getEntityIds(compareEntities); | ||
|
||
return Stream.of( | ||
getDeletedEntityDeltas(baseEntities, compareEntityIds), | ||
getKeptEntityDeltas(compareEntities, baseEntityMap, baseEntityIds) | ||
).flatMap(Collection::stream).toList(); | ||
} | ||
|
||
private @NotNull List<String> getEntityIds(Map<String, ?> entityMap) { | ||
return entityMap.keySet().stream().toList(); | ||
} | ||
|
||
private @NotNull List<String> getEntityIds(List<T> entities) { | ||
return entities.stream().map(this::getEntityId).toList(); | ||
} | ||
|
||
private @NotNull List<T> getBaseEntities(S base) { | ||
return base != null ? getEntities(base) : List.of(); | ||
} | ||
|
||
private @NotNull Map<String, T> getBaseEntityMap(List<T> baseEntities) { | ||
return baseEntities.stream().collect(Collectors.toMap(this::getEntityId, v -> v)); | ||
} | ||
|
||
private @NotNull List<GBFSEntityDelta<T>> getDeletedEntityDeltas(List<T> baseEntities, List<String> compareEntityIds) { | ||
return baseEntities.stream() | ||
.map(this::getEntityId) | ||
.filter(id -> !compareEntityIds.contains(id)) | ||
.map(id -> | ||
new GBFSEntityDelta<T>( | ||
id, | ||
DeltaType.DELETE, | ||
null | ||
)).toList(); | ||
} | ||
|
||
private @NotNull List<GBFSEntityDelta<T>> getKeptEntityDeltas(List<T> compareEntities, Map<String, T> baseEntityMap, List<String> baseEntityIds) { | ||
return compareEntities.stream() | ||
|
||
// We do not need to return a delta for entities that haven't changed. We trust the implementation | ||
// of equals from the gbfs model here. | ||
.filter(entity -> !entity.equals(baseEntityMap.get(getEntityId(entity)))) | ||
|
||
.map(entity -> { | ||
var entityId = getEntityId(entity); | ||
// If the entity exists in the base, then this delta is an update, and we can compute | ||
// the entity delta | ||
if (baseEntityIds.contains(entityId)) { | ||
return new GBFSEntityDelta<>( | ||
entityId, | ||
DeltaType.UPDATE, | ||
getEntityDelta(baseEntityMap.get(entityId), entity) | ||
); | ||
|
||
// Otherwise, this is a new entity, and the "delta" contains the entire entity | ||
} else { | ||
return new GBFSEntityDelta<>( | ||
entityId, | ||
DeltaType.CREATE, | ||
entity | ||
); | ||
} | ||
}).toList(); | ||
} | ||
|
||
private T getEntityDelta(T a, T b) { | ||
T delta = createEntity(); | ||
Method[] methods = a.getClass().getDeclaredMethods(); | ||
for (Method method : methods) { | ||
try { | ||
if (!EXCLUDE_METHODS.contains(method.getName()) && method.getParameterCount() == 0 && (method.invoke(a) == null || !method.invoke(a).equals(method.invoke(b)))) { | ||
getSetter(methods, method.getName()).ifPresent(setter -> { | ||
try { | ||
setter.invoke(delta, method.invoke(b)); | ||
} catch (IllegalAccessException | InvocationTargetException e) { | ||
throw new RuntimeException(e); | ||
} | ||
}); | ||
|
||
} | ||
} catch (InvocationTargetException | IllegalAccessException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
return delta; | ||
} | ||
|
||
private @NotNull Optional<Method> getSetter(Method[] methods, String getterName) { | ||
String setterName = getterName.replace("get", "set"); | ||
return Arrays.stream(methods).filter(method1 -> method1.getName().equals(setterName)).findFirst(); | ||
} | ||
|
||
/** | ||
* Get a list of enumerable entities from the GBFS file instance | ||
* @param instance The GBFS file instance | ||
* @return List of enumerable entities of type T | ||
*/ | ||
protected abstract List<T> getEntities(S instance); | ||
|
||
/** | ||
* Get the id of the entity | ||
* @param entity The entity | ||
* @return The entity's id | ||
*/ | ||
protected abstract String getEntityId(T entity); | ||
|
||
/** | ||
* Create a new instance of the entity of type T | ||
* @return An instance of T | ||
*/ | ||
protected abstract T createEntity(); | ||
|
||
/** | ||
* Get the last updated time of the GBFS file instance | ||
* @param instance The GBFS file instance | ||
* @return The last updated time | ||
*/ | ||
protected abstract long getLastUpdated(S instance); | ||
|
||
/** | ||
* Get the file name of the GBFS file | ||
* @return The file name | ||
*/ | ||
protected abstract String getFileName(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
* | ||
* | ||
* * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by | ||
* * the European Commission - subsequent versions of the EUPL (the "Licence"); | ||
* * You may not use this work except in compliance with the Licence. | ||
* * You may obtain a copy of the Licence at: | ||
* * | ||
* * https://joinup.ec.europa.eu/software/page/eupl | ||
* * | ||
* * Unless required by applicable law or agreed to in writing, software | ||
* * distributed under the Licence is distributed on an "AS IS" basis, | ||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* * See the Licence for the specific language governing permissions and | ||
* * limitations under the Licence. | ||
* | ||
*/ | ||
|
||
package org.entur.lamassu.delta; | ||
|
||
/** | ||
* Enum representing the type of delta. | ||
*/ | ||
public enum DeltaType { | ||
|
||
/** | ||
* A new entity was created | ||
*/ | ||
CREATE, | ||
|
||
/** | ||
* En existing entity was updated | ||
*/ | ||
UPDATE, | ||
|
||
/** | ||
* An existing entity was deleted | ||
*/ | ||
DELETE | ||
} |
33 changes: 33 additions & 0 deletions
33
src/main/java/org/entur/lamassu/delta/GBFSEntityDelta.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* | ||
* | ||
* * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by | ||
* * the European Commission - subsequent versions of the EUPL (the "Licence"); | ||
* * You may not use this work except in compliance with the Licence. | ||
* * You may obtain a copy of the Licence at: | ||
* * | ||
* * https://joinup.ec.europa.eu/software/page/eupl | ||
* * | ||
* * Unless required by applicable law or agreed to in writing, software | ||
* * distributed under the Licence is distributed on an "AS IS" basis, | ||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* * See the Licence for the specific language governing permissions and | ||
* * limitations under the Licence. | ||
* | ||
*/ | ||
|
||
package org.entur.lamassu.delta; | ||
|
||
/** | ||
* A record representing the difference (delta) between two enumerable entities of a GBFS file | ||
* @param entityId The unique ID of the entity | ||
* @param type The type of the delta (create, update or delete) | ||
* @param entity An instance of the entity itself, containing the changed fields. | ||
* Note: this field is non-null only if the delta type is "UPDATE" | ||
* @param <E> The type of the enumerable entity that was compared | ||
*/ | ||
public record GBFSEntityDelta<E>( | ||
String entityId, | ||
DeltaType type, | ||
E entity | ||
) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* | ||
* | ||
* * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by | ||
* * the European Commission - subsequent versions of the EUPL (the "Licence"); | ||
* * You may not use this work except in compliance with the Licence. | ||
* * You may obtain a copy of the Licence at: | ||
* * | ||
* * https://joinup.ec.europa.eu/software/page/eupl | ||
* * | ||
* * Unless required by applicable law or agreed to in writing, software | ||
* * distributed under the Licence is distributed on an "AS IS" basis, | ||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* * See the Licence for the specific language governing permissions and | ||
* * limitations under the Licence. | ||
* | ||
*/ | ||
|
||
package org.entur.lamassu.delta; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* Record representing the difference between two instances of a GBFS file | ||
* | ||
* @param base Numerical identifier of the base of the comparison | ||
* @param compare Numerical identifier of the compare side | ||
* @param fileName The file name of the GBFS files being compared | ||
* @param entityDelta A list of entity deltas for the enumerable entity of type E in the GBFS file | ||
* @param <E> The type of the enumerable entity in the GBFS file | ||
*/ | ||
public record GBFSFileDelta<E>( | ||
Long base, | ||
Long compare, | ||
String fileName, | ||
List<GBFSEntityDelta<E>> entityDelta | ||
) {} |
43 changes: 43 additions & 0 deletions
43
src/main/java/org/entur/lamassu/delta/GBFSFileDeltaCalculator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* | ||
* | ||
* * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by | ||
* * the European Commission - subsequent versions of the EUPL (the "Licence"); | ||
* * You may not use this work except in compliance with the Licence. | ||
* * You may obtain a copy of the Licence at: | ||
* * | ||
* * https://joinup.ec.europa.eu/software/page/eupl | ||
* * | ||
* * Unless required by applicable law or agreed to in writing, software | ||
* * distributed under the Licence is distributed on an "AS IS" basis, | ||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* * See the Licence for the specific language governing permissions and | ||
* * limitations under the Licence. | ||
* | ||
*/ | ||
|
||
package org.entur.lamassu.delta; | ||
|
||
import org.jetbrains.annotations.NotNull; | ||
|
||
/** | ||
* Represents a calculator that can compute the delta (difference) between to instances | ||
* of a GBFS file. | ||
* | ||
* @param <S> The type of the GBFS file instances to compare | ||
* @param <T> The type of the enumerable entity inside the GBFS file being compared | ||
*/ | ||
public interface GBFSFileDeltaCalculator<S, T> { | ||
|
||
/** | ||
* Calculate the delta (difference) between to instances of a GBFS file of type S | ||
* | ||
* @param base The base of the comparison | ||
* Note: This parameter can be null, in it is then interpreted as a full update | ||
* | ||
* @param compare The instance to compare with the base | ||
* Note: This parameter can't be null | ||
* @return An instance of GBFSFileDelta containing deltas of the enumerable entity of type T | ||
*/ | ||
GBFSFileDelta<T> calculateDelta(S base, @NotNull S compare); | ||
} |
54 changes: 54 additions & 0 deletions
54
src/main/java/org/entur/lamassu/delta/GBFSStationStatusDeltaCalculator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* | ||
* | ||
* * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by | ||
* * the European Commission - subsequent versions of the EUPL (the "Licence"); | ||
* * You may not use this work except in compliance with the Licence. | ||
* * You may obtain a copy of the Licence at: | ||
* * | ||
* * https://joinup.ec.europa.eu/software/page/eupl | ||
* * | ||
* * Unless required by applicable law or agreed to in writing, software | ||
* * distributed under the Licence is distributed on an "AS IS" basis, | ||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* * See the Licence for the specific language governing permissions and | ||
* * limitations under the Licence. | ||
* | ||
*/ | ||
|
||
package org.entur.lamassu.delta; | ||
|
||
import org.mobilitydata.gbfs.v3_0.station_status.GBFSStation; | ||
import org.mobilitydata.gbfs.v3_0.station_status.GBFSStationStatus; | ||
|
||
import java.util.List; | ||
|
||
public class GBFSStationStatusDeltaCalculator extends BaseGBFSFileDeltaCalculator<GBFSStationStatus, GBFSStation> { | ||
|
||
public static final String FILE_NAME = "station_status"; | ||
|
||
@Override | ||
protected List<GBFSStation> getEntities(GBFSStationStatus instance) { | ||
return instance.getData().getStations(); | ||
} | ||
|
||
@Override | ||
protected String getEntityId(GBFSStation entity) { | ||
return entity.getStationId(); | ||
} | ||
|
||
@Override | ||
protected GBFSStation createEntity() { | ||
return new GBFSStation(); | ||
} | ||
|
||
@Override | ||
protected long getLastUpdated(GBFSStationStatus instance) { | ||
return instance.getLastUpdated().getTime(); | ||
} | ||
|
||
@Override | ||
protected String getFileName() { | ||
return FILE_NAME; | ||
} | ||
} |
Oops, something went wrong.