Added link to Weblate from translatable strings once the translator mode

is activated in the workspace settings. This required the addition of a
new lib (in source form) for a SipHash implementation.
This commit is contained in:
Zukero
2017-04-10 18:16:17 +02:00
parent bd8576df0c
commit 0a7cb40dbc
31 changed files with 1485 additions and 8 deletions

View File

@@ -3,6 +3,7 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="res"/>
<classpathentry kind="src" path="hacked-libtiled"/>
<classpathentry kind="src" path="siphash-zackehh/src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="lib/jide-oss.jar"/>
<classpathentry kind="lib" path="lib/json_simple-1.1.jar"/>

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Isaac Whitfield
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

19
siphash-zackehh/.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# IntelliJ
.idea
*.iml
# build
target

View File

@@ -0,0 +1,3 @@
language: java
script:
- mvn clean test jacoco:report coveralls:report

21
siphash-zackehh/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Isaac Whitfield
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

77
siphash-zackehh/README.md Normal file
View File

@@ -0,0 +1,77 @@
# SipHash
[![Build Status](https://travis-ci.org/zackehh/siphash-java.svg?branch=master)](https://travis-ci.org/zackehh/siphash-java) [![Coverage Status](https://coveralls.io/repos/zackehh/siphash-java/badge.svg?branch=master&service=github)](https://coveralls.io/github/zackehh/siphash-java?branch=master)
A Java implementation of the SipHash cryptographic hash family. Supports any variation, although defaults to the widely used SipHash-2-4. Can be used with either full input, or used as a streaming digest.
This library was heavily influenced by [veorq's C implementation](https://github.com/veorq/siphash) and [Forward C&C's reference implementation](http://www.forward.com.au/pfod/SipHashJavaLibrary/) - I just decided it was time a Java implementation of SipHash made it onto Maven :).
## Setup
`siphash` is available on Maven central, via Sonatype OSS:
```
<dependency>
<groupId>com.zackehh</groupId>
<artifactId>siphash</artifactId>
<version>1.0.0</version>
</dependency>
```
## Usage
There are two ways of using SipHash (see below). Both return a `SipHashResult` which can be used to retrieve the result in various forms. All constructors can take arguments to specify the compression rounds. For further usage, please visit the [documentation](http://www.javadoc.io/doc/com.zackehh/siphash).
#### Full Input Hash
The first is to simple create a `SipHash` instance and use it to repeatedly hash using the same key.
The internal state is immutable, so you can hash many inputs without having to recreate a new `SipHash` instance (unless you want a new key).
```java
SipHash hasher = new SipHash("0123456789ABCDEF".getBytes());
SipHashResult result = hasher.hash("my-input".getBytes());
System.out.println(result.get()); // 182795880124085484 <-- this can overflow
System.out.println(result.getHex()); // "2896be26d3374ec"
System.out.println(result.getHex(true)); // "02896be26d3374ec"
System.out.println(result.getHex(SipHashCase.UPPER)); // "2896BE26D3374EC"
System.out.println(result.getHex(true, SipHashCase.UPPER)); // "02896BE26D3374EC"
```
#### Streaming Hash
The second is to use the library as a streaming hash, meaning you can apply chunks of bytes to the hash as they become available.
Using this method you must create a new digest every time you want to hash a different input as the internal state is mutable.
```java
SipHashDigest digest = new SipHashDigest("0123456789ABCDEF".getBytes());
digest.update("chu".getBytes());
digest.update("nked".getBytes());
digest.update(" string".getBytes());
SipHashResult result = digest.finish();
System.out.println(result.get()); // 3502906798476177428 <-- this can overflow
System.out.println(result.getHex()); // "309cd32c8c793014"
System.out.println(result.getHex(true)); // "309cd32c8c793014"
System.out.println(result.getHex(SipHashCase.UPPER)); // "309CD32C8C793014"
System.out.println(result.getHex(true, SipHashCase.UPPER)); // "309CD32C8C793014"
```
## Contributing
If you wish to contribute (awesome!), please file an issue first! All PRs should pass `mvn clean verify` and maintain 100% test coverage.
## Testing
Tests are run using `mvn`. I aim to maintain 100% coverage where possible (both line and branch).
Tests can be run as follows:
```bash
$ mvn clean verify
```

171
siphash-zackehh/pom.xml Normal file
View File

@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zackehh</groupId>
<artifactId>siphash</artifactId>
<version>1.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SipHash</name>
<description>
A SipHash implementation in Java.
</description>
<url>https://github.com/zackehh/siphash-java</url>
<properties>
<jacoco.version>0.7.5.201505241946</jacoco.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<developers>
<developer>
<id>iwhitfield</id>
<name>Isaac Whitfield</name>
<email>iw@zackehh.com</email>
<organization>Appcelerator, Inc.</organization>
<organizationUrl>http://www.appcelerator.com</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git@github.com:zackehh/siphash-java.git</connection>
<developerConnection>scm:git:git@github.com:zackehh/siphash-java.git</developerConnection>
<url>git@github.com:zackehh/siphash-java.git</url>
</scm>
<licenses>
<license>
<name>MIT License</name>
<url>http://www.opensource.org/licenses/mit-license.php</url>
</license>
</licenses>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.3</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>false</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>4.1.0</version>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,106 @@
package com.zackehh.siphash;
import static com.zackehh.siphash.SipHashConstants.*;
/**
* Main entry point for SipHash, providing a basic hash
* interface. Assuming you have your full String to hash,
* you can simply provide it to ${@link SipHash#hash(byte[])}.
*
* This class can be initialized and stored in case the
* developer wishes to use the same key over and over again.
*
* This avoids the overhead of having to create the initial
* key over and over again.
*
* <pre>
* {@code
* List<String> inputs = Arrays.asList("input1", "input2", "input3");
* SipHash hasher = new SipHash("this key is mine".getBytes());
* for (int i = 0; i < inputs.size(); i++) {
* hasher.hash(inputs.get(i));
* }
* }
* </pre>
*/
public class SipHash {
/**
* The values of SipHash-c-d, to determine which of the SipHash
* family we're using for this hash.
*/
private final int c, d;
/**
* Initial seeded value of v0.
*/
private final long v0;
/**
* Initial seeded value of v1.
*/
private final long v1;
/**
* Initial seeded value of v2.
*/
private final long v2;
/**
* Initial seeded value of v3.
*/
private final long v3;
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This uses the default values of
* c/d, meaning that we default to SipHash-2-4.
*
* @param key a 16 byte key input
*/
public SipHash(byte[] key){
this(key, DEFAULT_C, DEFAULT_D);
}
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This constructor allows for
* providing the c/d values, allowing the developer to
* select any of the SipHash family to use for hashing.
*
* @param key a 16 byte key input
* @param c the number of compression rounds
* @param d the number of finalization rounds
*/
public SipHash(byte[] key, int c, int d){
this.c = c;
this.d = d;
SipHashKey hashKey = new SipHashKey(key);
this.v0 = (INITIAL_V0 ^ hashKey.k0);
this.v1 = (INITIAL_V1 ^ hashKey.k1);
this.v2 = (INITIAL_V2 ^ hashKey.k0);
this.v3 = (INITIAL_V3 ^ hashKey.k1);
}
/**
* The basic hash implementation provided in the library.
* Assuming you have your full input, you can provide it and
* it will be hashed based on the values which were provided
* to the constructor of this class.
*
* @param data the bytes to hash
* @return a ${@link SipHashResult} instance
*/
public SipHashResult hash(byte[] data) {
SipHashDigest digest = new SipHashDigest(v0, v1, v2, v3, c, d);
for (byte aData : data) {
digest.update(aData);
}
return digest.finish();
}
}

View File

@@ -0,0 +1,8 @@
package com.zackehh.siphash;
/**
* A basic enum to determine the case of a String.
*
* A String can either be UPPER or LOWER case.
*/
public enum SipHashCase { UPPER, LOWER }

View File

@@ -0,0 +1,63 @@
package com.zackehh.siphash;
/**
* Class containing several constants for use alongside
* hashing. Fields such as initial states and defaults,
* as they will not change throughout hashing.
*/
class SipHashConstants {
/**
* This constructor is private, nobody should be
* accessing it!
*
* @throws IllegalAccessException
*/
private SipHashConstants() throws IllegalAccessException {
throw new IllegalAccessException();
}
/**
* Initial magic number for v0.
*/
static final long INITIAL_V0 = 0x736f6d6570736575L;
/**
* Initial magic number for v1.
*/
static final long INITIAL_V1 = 0x646f72616e646f6dL;
/**
* Initial magic number for v2.
*/
static final long INITIAL_V2 = 0x6c7967656e657261L;
/**
* Initial magic number for v3.
*/
static final long INITIAL_V3 = 0x7465646279746573L;
/**
* The default number of rounds of compression during per block.
* This defaults to 2 as the default implementation is SipHash-2-4.
*/
static final int DEFAULT_C = 2;
/**
* The default number of rounds of compression during finalization.
* This defaults to 4 as the default implementation is SipHash-2-4.
*/
static final int DEFAULT_D = 4;
/**
* Whether or not we should pad any hashes by default.
*/
static final boolean DEFAULT_PADDING = false;
/**
* The default String casing for any output Hex Strings. We default
* to lower case as it's the least expensive path.
*/
static final SipHashCase DEFAULT_CASE = SipHashCase.LOWER;
}

View File

@@ -0,0 +1,225 @@
package com.zackehh.siphash;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_C;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_D;
/**
* A streaming implementation of SipHash, to be used when
* you don't have all input available at the same time. Chunks
* of bytes can be applied as they're received, and will be hashed
* accordingly.
*
* As with ${@link SipHash}, the compression and finalization rounds
* can be customized.
*/
public class SipHashDigest {
/**
* The values of SipHash-c-d, to determine which of the SipHash
* family we're using for this hash.
*/
private final int c, d;
/**
* Initial seeded value of v0.
*/
private long v0;
/**
* Initial seeded value of v1.
*/
private long v1;
/**
* Initial seeded value of v2.
*/
private long v2;
/**
* Initial seeded value of v3.
*/
private long v3;
/**
* A counter to keep track of the length of the input.
*/
private byte input_len = 0;
/**
* A counter to keep track of the current position inside
* of a chunk of bytes. Seeing as bytes are applied in chunks
* of 8, this is necessary.
*/
private int m_idx = 0;
/**
* The `m` value from the SipHash algorithm. Every 8 bytes, this
* value will be applied to the current state of the hash.
*/
private long m;
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This uses the default values of
* c/d, meaning that we default to SipHash-2-4.
*
* @param key a 16 byte key input
*/
public SipHashDigest(byte[] key) {
this(key, DEFAULT_C, DEFAULT_D);
}
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This constructor allows for
* providing the c/d values, allowing the developer to
* select any of the SipHash family to use for hashing.
*
* @param key a 16 byte key input
* @param c the number of compression rounds
* @param d the number of finalization rounds
*/
public SipHashDigest(byte[] key, int c, int d) {
this.c = c;
this.d = d;
SipHashKey hashKey = new SipHashKey(key);
this.v0 = SipHashConstants.INITIAL_V0 ^ hashKey.k0;
this.v1 = SipHashConstants.INITIAL_V1 ^ hashKey.k1;
this.v2 = SipHashConstants.INITIAL_V2 ^ hashKey.k0;
this.v3 = SipHashConstants.INITIAL_V3 ^ hashKey.k1;
}
/**
* This constructor is used by the ${@link SipHash} implementation,
* and takes an initial (seeded) value of v0/v1/v2/v3. This is used
* when the key has been pre-calculated. This constructor also
* receives the values of `c` and `d` to use in this hash.
*
* @param v0 an initial seeded v0
* @param v1 an initial seeded v1
* @param v2 an initial seeded v2
* @param v3 an initial seeded v3
* @param c the number of compression rounds
* @param d the number of finalization rounds
*/
SipHashDigest(long v0, long v1, long v2, long v3, int c, int d) {
this.c = c;
this.d = d;
this.v0 = v0;
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
}
/**
* Updates the current state of the hash with a single byte. This
* is the streaming implementation which shifts as required to ensure
* we can take an arbitrary number of bytes at any given time. We only
* apply the block once the index (`m_idx`) has reached 8. The number
* of compression rounds is determined by the `c` value passed in by
* the developer.
*
* This method returns this instance, as a way of allowing the developer
* to chain.
*
* @return a ${@link SipHashDigest} instance
*/
public SipHashDigest update(byte b) {
input_len++;
m |= (((long) b & 0xff) << (m_idx * 8));
m_idx++;
if (m_idx >= 8) {
v3 ^= m;
for (int i = 0; i < c; i++) {
round();
}
v0 ^= m;
m_idx = 0;
m = 0;
}
return this;
}
/**
* A convenience method to allow passing a chunk of bytes at once, rather
* than a byte at a time.
*
* @return a ${@link SipHashDigest} instance
*/
public SipHashDigest update(byte[] bytes) {
for (byte b : bytes) {
update(b);
}
return this;
}
/**
* Finalizes the hash by padding 0s until the next multiple of
* 8 (as we operate in 8 byte chunks). The last byte added to
* the hash is the length of the input, which we keep inside the
* `input_len` counter. The number of rounds is based on the value
* of `d` as specified by the developer.
*
* This method returns a ${@link SipHashResult}, as no further updates
* should occur (i.e. the lack of chaining here shows we're done).
*
* @return a ${@link SipHashResult} instance
*/
public SipHashResult finish() {
byte msgLenMod256 = input_len;
while (m_idx < 7) {
update((byte) 0);
}
update(msgLenMod256);
v2 ^= 0xff;
for (int i = 0; i < d; i++) {
round();
}
return new SipHashResult(v0 ^ v1 ^ v2 ^ v3);
}
/**
* Performs the equivalent of SipRound on the provided state.
* This method affects the state of this digest, in that it
* mutates the v states directly.
*/
private void round() {
v0 += v1;
v2 += v3;
v1 = rotateLeft(v1, 13);
v3 = rotateLeft(v3, 16);
v1 ^= v0;
v3 ^= v2;
v0 = rotateLeft(v0, 32);
v2 += v1;
v0 += v3;
v1 = rotateLeft(v1, 17);
v3 = rotateLeft(v3, 21);
v1 ^= v2;
v3 ^= v0;
v2 = rotateLeft(v2, 32);
}
/**
* Rotates an input number `val` left by `shift` number of bits. Bits which are
* pushed off to the left are rotated back onto the right, making this a left
* rotation (a circular shift).
*
* @param val the value to be shifted
* @param shift how far left to shift
* @return a long value once shifted
*/
private long rotateLeft(long val, int shift) {
return (val << shift) | val >>> (64 - shift);
}
}

View File

@@ -0,0 +1,53 @@
package com.zackehh.siphash;
/**
* A container class to store both k0 and k1. These
* values are created from a 16 byte key passed into
* the constructor. This isn't ideal as it's another
* alloc, but it'll do for now.
*/
class SipHashKey {
/**
* The value of k0.
*/
final long k0;
/**
* The value of k1.
*/
final long k1;
/**
* Accepts a 16 byte input key and converts the
* first and last 8 byte chunks to little-endian.
* These values become k0 and k1.
*
* @param key the 16 byte key input
*/
public SipHashKey(byte[] key) {
if (key.length != 16) {
throw new IllegalArgumentException("Key must be exactly 16 bytes!");
}
this.k0 = bytesToLong(key, 0);
this.k1 = bytesToLong(key, 8);
}
/**
* Converts a chunk of 8 bytes to a number in little-endian
* format. Accepts an offset to determine where the chunk
* begins in the byte array.
*
* @param b our byte array
* @param offset the index to start at
* @return a little-endian long representation
*/
private static long bytesToLong(byte[] b, int offset) {
long m = 0;
for (int i = 0; i < 8; i++) {
m |= ((((long) b[i + offset]) & 0xff) << (8 * i));
}
return m;
}
}

View File

@@ -0,0 +1,121 @@
package com.zackehh.siphash;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_CASE;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_PADDING;
/**
* A container class of the result of a hash. This class exists
* to allow the developer to retrieve the result in any format
* they like. Currently available formats are `long` and ${@link java.lang.String}.
* When retrieving as a String, the developer can specify the case
* they want it in, and whether or not we should pad the left side
* to 16 characters with 0s.
*/
public class SipHashResult {
/**
* The internal hash result.
*/
private final long result;
/**
* A package-private constructor, as only
* SipHash should be creating results.
*
* @param result the result of a hash
*/
SipHashResult(long result){
this.result = result;
}
/**
* Simply returns the hash result as a long.
*
* @return the hash value as a long
*/
public long get(){
return result;
}
/**
* Returns the result as a Hex String, using
* the default padding and casing values.
*
* @return the hash value as a Hex String
*/
public String getHex(){
return getHex(DEFAULT_PADDING, DEFAULT_CASE);
}
/**
* Returns the result as a Hex String, using
* a custom padding value and default casing value.
*
* @param padding whether or not to pad the string
* @return the hash value as a Hex String
*/
public String getHex(boolean padding){
return getHex(padding, DEFAULT_CASE);
}
/**
* Returns the result as a Hex String, using
* a default padding value and custom casing value.
*
* @param s_case the case to convert the output to
* @return the hash value as a Hex String
*/
public String getHex(SipHashCase s_case){
return getHex(DEFAULT_PADDING, s_case);
}
/**
* Returns the result as a Hex String, taking in
* various arguments to customize the output further,
* such as casing and padding.
*
* @param padding whether or not to pad the string
* @param s_case the case to convert the output to
* @return a Hex String in the custom format
*/
public String getHex(boolean padding, SipHashCase s_case){
String str = Long.toHexString(get());
if (padding) {
str = leftPad(str, 16, "0");
}
if (s_case == SipHashCase.UPPER) {
str = str.toUpperCase();
}
return str;
}
/**
* Modified for https://github.com/Zukero/ATCS
* Replaces the StringUtils.leftPad from apache commons, to remove dependency.
*
* @param str the string to pad
* @param len the total desired length
* @param pad the padding string
* @return str prefixed with enough repetitions of the pad to have a total length matching len
*/
public String leftPad(String str, int len, String pad) {
StringBuilder sb = new StringBuilder(len);
int padlen = len - str.length();
int partialPadLen = padlen % pad.length();
int padCount = padlen / pad.length();
while (padCount >= 0) {
sb.append(pad);
padCount--;
}
if (partialPadLen > 0) {
sb.append(pad.substring(0, partialPadLen));
}
sb.append(str);
return sb.toString();
}
}

View File

@@ -0,0 +1,27 @@
package com.zackehh.siphash;
import org.testng.Assert;
import org.testng.annotations.Test;
public class SipHashCaseTest {
@Test
public void ensureAllValues() throws Exception {
SipHashCase[] cases = SipHashCase.values();
Assert.assertEquals(cases[0], SipHashCase.UPPER);
Assert.assertEquals(cases[1], SipHashCase.LOWER);
}
@Test
public void ensureValueOf() throws Exception {
Assert.assertEquals(SipHashCase.valueOf("UPPER"), SipHashCase.UPPER);
Assert.assertEquals(SipHashCase.valueOf("LOWER"), SipHashCase.LOWER);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void invalidCaseValueOf() throws Exception {
SipHashCase.valueOf("invalid");
}
}

View File

@@ -0,0 +1,32 @@
package com.zackehh.siphash;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
public class SipHashConstantsTest {
@Test
public void ensureAllConstants() throws Exception {
Assert.assertEquals(SipHashConstants.INITIAL_V0, 0x736f6d6570736575L);
Assert.assertEquals(SipHashConstants.INITIAL_V1, 0x646f72616e646f6dL);
Assert.assertEquals(SipHashConstants.INITIAL_V2, 0x6c7967656e657261L);
Assert.assertEquals(SipHashConstants.INITIAL_V3, 0x7465646279746573L);
Assert.assertEquals(SipHashConstants.DEFAULT_C, 2);
Assert.assertEquals(SipHashConstants.DEFAULT_D, 4);
Assert.assertEquals(SipHashConstants.DEFAULT_CASE, SipHashCase.LOWER);
Assert.assertEquals(SipHashConstants.DEFAULT_PADDING, false);
}
@Test(expectedExceptions = InvocationTargetException.class)
public void ensureCannotInstance() throws Exception {
Constructor<SipHashConstants> ctor = SipHashConstants.class.getDeclaredConstructor();
ctor.setAccessible(true);
Assert.assertTrue(Modifier.isPrivate(ctor.getModifiers()));
ctor.newInstance();
}
}

View File

@@ -0,0 +1,148 @@
package com.zackehh.siphash;
import org.testng.Assert;
import org.testng.annotations.Test;
public class SipHashDigestTest {
@Test
public void initializeDigestWithKey() throws Exception {
SipHashDigest sipHash = new SipHashDigest("0123456789ABCDEF".getBytes());
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, SipHashConstants.DEFAULT_C);
Assert.assertEquals(d, SipHashConstants.DEFAULT_D);
}
@Test
public void initializeDigestWithKeyAndCD() throws Exception {
SipHashDigest sipHash = new SipHashDigest("0123456789ABCDEF".getBytes(), 4, 8);
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, 4);
Assert.assertEquals(d, 8);
}
@Test
public void initializeDigestWithKeyThenHash() throws Exception {
SipHashDigest sipHash = new SipHashDigest("0123456789ABCDEF".getBytes());
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, SipHashConstants.DEFAULT_C);
Assert.assertEquals(d, SipHashConstants.DEFAULT_D);
for(byte b : "zymotechnics".getBytes()){
sipHash.update(b);
}
SipHashResult hashResult = sipHash.finish();
Assert.assertEquals(hashResult.get(), 699588702094987020L);
Assert.assertEquals(hashResult.getHex(), "9b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(true), "09b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(SipHashCase.UPPER), "9B57037CD3F8F0C");
Assert.assertEquals(hashResult.getHex(true, SipHashCase.UPPER), "09B57037CD3F8F0C");
}
@Test
public void initializeStateWithKeyAndCDThenHash() throws Exception {
SipHashDigest sipHash = new SipHashDigest("0123456789ABCDEF".getBytes(), 4, 8);
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, 4);
Assert.assertEquals(d, 8);
for(byte b : "zymotechnics".getBytes()){
sipHash.update(b);
}
SipHashResult hashResult = sipHash.finish();
Assert.assertEquals(hashResult.get(), -3891084581787974112L); // overflow
Assert.assertEquals(hashResult.getHex(), "ca0017304f874620");
Assert.assertEquals(hashResult.getHex(true), "ca0017304f874620");
Assert.assertEquals(hashResult.getHex(SipHashCase.UPPER), "CA0017304F874620");
Assert.assertEquals(hashResult.getHex(true, SipHashCase.UPPER), "CA0017304F874620");
}
@Test
public void updateWithAByteArrayChunk() throws Exception {
SipHashDigest sipHash = new SipHashDigest("0123456789ABCDEF".getBytes());
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, SipHashConstants.DEFAULT_C);
Assert.assertEquals(d, SipHashConstants.DEFAULT_D);
sipHash.update("zymo".getBytes());
sipHash.update("techni".getBytes());
sipHash.update("cs".getBytes());
SipHashResult hashResult = sipHash.finish();
Assert.assertEquals(hashResult.get(), 699588702094987020L);
Assert.assertEquals(hashResult.getHex(), "9b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(true), "09b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(SipHashCase.UPPER), "9B57037CD3F8F0C");
Assert.assertEquals(hashResult.getHex(true, SipHashCase.UPPER), "09B57037CD3F8F0C");
}
}

View File

@@ -0,0 +1,27 @@
package com.zackehh.siphash;
import org.testng.Assert;
import org.testng.annotations.Test;
public class SipHashKeyTest {
@Test
public void initializeWithKey() throws Exception {
SipHashKey key = new SipHashKey("0123456789ABCDEF".getBytes());
long k0 = SipHashTestUtils.getPrivateField(key, "k0", Long.class);
long k1 = SipHashTestUtils.getPrivateField(key, "k1", Long.class);
Assert.assertEquals(k0, 3978425819141910832L);
Assert.assertEquals(k1, 5063528411713059128L);
}
@Test(
expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = "Key must be exactly 16 bytes!"
)
public void initializeWithKeyTooLong() throws Exception {
new SipHashKey("0123456789ABCDEFG".getBytes()); // 17 bytes
}
}

View File

@@ -0,0 +1,107 @@
package com.zackehh.siphash;
import org.testng.Assert;
import org.testng.annotations.Test;
public class SipHashTest {
@Test
public void initializeStateWithKey() throws Exception {
SipHash sipHash = new SipHash("0123456789ABCDEF".getBytes());
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, SipHashConstants.DEFAULT_C);
Assert.assertEquals(d, SipHashConstants.DEFAULT_D);
}
@Test
public void initializeStateWithKeyAndCD() throws Exception {
SipHash sipHash = new SipHash("0123456789ABCDEF".getBytes(), 4, 8);
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, 4);
Assert.assertEquals(d, 8);
}
@Test
public void initializeStateWithKeyThenHash() throws Exception {
SipHash sipHash = new SipHash("0123456789ABCDEF".getBytes());
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, SipHashConstants.DEFAULT_C);
Assert.assertEquals(d, SipHashConstants.DEFAULT_D);
SipHashResult hashResult = sipHash.hash("zymotechnics".getBytes());
Assert.assertEquals(hashResult.get(), 699588702094987020L);
Assert.assertEquals(hashResult.getHex(), "9b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(true), "09b57037cd3f8f0c");
Assert.assertEquals(hashResult.getHex(SipHashCase.UPPER), "9B57037CD3F8F0C");
Assert.assertEquals(hashResult.getHex(true, SipHashCase.UPPER), "09B57037CD3F8F0C");
}
@Test
public void initializeStateWithKeyAndCDThenHash() throws Exception {
SipHash sipHash = new SipHash("0123456789ABCDEF".getBytes(), 4, 8);
long v0 = SipHashTestUtils.getPrivateField(sipHash, "v0", Long.class);
long v1 = SipHashTestUtils.getPrivateField(sipHash, "v1", Long.class);
long v2 = SipHashTestUtils.getPrivateField(sipHash, "v2", Long.class);
long v3 = SipHashTestUtils.getPrivateField(sipHash, "v3", Long.class);
Assert.assertEquals(v0, 4925064773550298181L);
Assert.assertEquals(v1, 2461839666708829781L);
Assert.assertEquals(v2, 6579568090023412561L);
Assert.assertEquals(v3, 3611922228250500171L);
int c = SipHashTestUtils.getPrivateField(sipHash, "c", Integer.class);
int d = SipHashTestUtils.getPrivateField(sipHash, "d", Integer.class);
Assert.assertEquals(c, 4);
Assert.assertEquals(d, 8);
SipHashResult hashResult = sipHash.hash("zymotechnics".getBytes());
Assert.assertEquals(hashResult.get(), -3891084581787974112L); // overflow
Assert.assertEquals(hashResult.getHex(), "ca0017304f874620");
Assert.assertEquals(hashResult.getHex(true), "ca0017304f874620");
Assert.assertEquals(hashResult.getHex(SipHashCase.UPPER), "CA0017304F874620");
Assert.assertEquals(hashResult.getHex(true, SipHashCase.UPPER), "CA0017304F874620");
}
}

View File

@@ -0,0 +1,13 @@
package com.zackehh.siphash;
import java.lang.reflect.Field;
public class SipHashTestUtils {
static <T> T getPrivateField(Object obj, String name, Class<T> clazz) throws Exception {
Field f = obj.getClass().getDeclaredField(name);
f.setAccessible(true);
return clazz.cast(f.get(obj));
}
}

View File

@@ -19,6 +19,8 @@ import com.gpl.rpg.atcontentstudio.model.Workspace;
import com.gpl.rpg.atcontentstudio.ui.StudioFrame;
import com.gpl.rpg.atcontentstudio.ui.WorkerDialog;
import com.gpl.rpg.atcontentstudio.ui.WorkspaceSelector;
import com.gpl.rpg.atcontentstudio.utils.HashUtils;
import com.zackehh.siphash.SipHash;
public class ATContentStudio {

View File

@@ -11,6 +11,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.ComboBoxModel;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
@@ -39,8 +41,11 @@ public class WorkspaceSettings {
public static Boolean DEFAULT_USE_SYS_IMG_EDITOR = false;
public Setting<Boolean> useSystemDefaultImageEditor = new PrimitiveSetting<Boolean>("useSystemDefaultImageEditor", DEFAULT_USE_SYS_IMG_EDITOR);
public static String DEFAULT_IMG_EDITOR_COMMAND = "gimp";
public static String[] LANGUAGE_LIST = new String[]{null, "de", "ru", "pl", "fr", "it", "es", "nl", "uk", "ca", "sv", "pt", "pt_BR", "zh_Hant", "zh_Hans", "ja", "cs", "tr", "ko", "hu", "sl", "bg", "id", "fi", "th", "gl", "ms" ,"pa", "az"};
public Setting<String> imageEditorCommand = new PrimitiveSetting<String>("imageEditorCommand", DEFAULT_IMG_EDITOR_COMMAND);
public Setting<String> translatorLanguage = new NullDefaultPrimitiveSetting<String>("translatorLanguage");
public List<Setting<? extends Object>> settings = new ArrayList<Setting<? extends Object>>();
public WorkspaceSettings(Workspace parent) {
@@ -50,6 +55,7 @@ public class WorkspaceSettings {
settings.add(useSystemDefaultImageViewer);
settings.add(useSystemDefaultImageEditor);
settings.add(imageEditorCommand);
settings.add(translatorLanguage);
file = new File(parent.baseFolder, FILENAME);
if (file.exists()) {
load(file);
@@ -174,6 +180,18 @@ public class WorkspaceSettings {
}
public class NullDefaultPrimitiveSetting<X extends Object> extends PrimitiveSetting<X> {
public NullDefaultPrimitiveSetting(String id) {
super(id, null);
}
@Override
public void saveToJson(Map json) {
if (value != null) json.put(id, value);
}
}
public class ListSetting<X extends Object> extends Setting<List<X>> {
public ListSetting(String id, List<X> defaultValue) {

View File

@@ -72,6 +72,9 @@ public class AboutEditor extends Editor {
"<a href=\"http://www.beanshell.org/\">BeanShell</a> by Pat Niemeyer.<br/>" +
"License: <a href=\"http://www.beanshell.org/license.html\">LGPL v3</a><br/>" +
"<br/>" +
"A slightly modified version of <a href=\"https://github.com/zackehh/siphash-java\">SipHash for Java</a> by Isaac Whitfield.<br/>" +
"License: <a href=\"https://github.com/zackehh/siphash-java/blob/master/LICENSE\">MIT License</a><br/>" +
"<br/>" +
"See the tabs below to find the full license text for each of these.<br/>" +
"<br/>" +
"The Windows installer was created with:<br/>" +
@@ -121,6 +124,7 @@ public class AboutEditor extends Editor {
editorTabsHolder.add("libtiled-java License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.libtiled.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
editorTabsHolder.add("prefuse License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/license-prefuse.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
editorTabsHolder.add("BeanShell License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.LGPLv3.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
editorTabsHolder.add("SipHash for Java License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.siphash-zackehh.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
editorTabsHolder.add("ATCS License", getInfoPane(new Scanner(ATContentStudio.class.getResourceAsStream("/LICENSE.GPLv3.txt"), "UTF-8").useDelimiter("\\A").next(), "text/text"));
}

View File

@@ -2,6 +2,8 @@ package com.gpl.rpg.atcontentstudio.ui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
@@ -10,6 +12,9 @@ import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -48,6 +53,7 @@ import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.Project;
import com.gpl.rpg.atcontentstudio.model.ProjectElementListener;
import com.gpl.rpg.atcontentstudio.model.Workspace;
import com.gpl.rpg.atcontentstudio.model.gamedata.ActorCondition;
import com.gpl.rpg.atcontentstudio.model.gamedata.Dialogue;
import com.gpl.rpg.atcontentstudio.model.gamedata.Droplist;
@@ -57,6 +63,7 @@ import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement;
import com.gpl.rpg.atcontentstudio.model.gamedata.NPC;
import com.gpl.rpg.atcontentstudio.model.gamedata.Quest;
import com.gpl.rpg.atcontentstudio.model.maps.TMXMap;
import com.gpl.rpg.atcontentstudio.utils.HashUtils;
import com.jidesoft.swing.ComboBoxSearchable;
import com.jidesoft.swing.JideBoxLayout;
@@ -108,6 +115,58 @@ public abstract class Editor extends JPanel implements ProjectElementListener {
return addTextField(pane, label, value, false, nullListener);
}
public static JTextField addTranslatableTextField(JPanel pane, String label, String initialValue, boolean editable, final FieldUpdateListener listener) {
final JTextField tfField = addTextField(pane, label, initialValue, editable, listener);
if (Workspace.activeWorkspace.settings.translatorLanguage.getCurrentValue() != null) {
final JLabel translateLinkLabel = new JLabel(getWeblateLabelLink(initialValue));
pane.add(translateLinkLabel, JideBoxLayout.FIX);
tfField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfField.getText()));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
@Override
public void insertUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfField.getText()));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
@Override
public void changedUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfField.getText()));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
});
translateLinkLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
translateLinkLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
try {
Desktop.getDesktop().browse(new URI(getWeblateLabelURI(tfField.getText())));
} catch (IOException e1) {
e1.printStackTrace();
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
}
}
});
}
return tfField;
}
public static String getWeblateLabelLink(String text) {
return "<html><a href=\""+getWeblateLabelURI(text)+"\">Translate on weblate</a></html>";
}
public static String getWeblateLabelURI(String text) {
return "https://hosted.weblate.org/translate/andors-trail/game-content/"+Workspace.activeWorkspace.settings.translatorLanguage.getCurrentValue()+"/?checksum="+HashUtils.weblateHash(text, "");
}
public static JTextField addTextField(JPanel pane, String label, String initialValue, boolean editable, final FieldUpdateListener listener) {
JPanel tfPane = new JPanel();
tfPane.setLayout(new JideBoxLayout(tfPane, JideBoxLayout.LINE_AXIS, 6));
@@ -151,6 +210,51 @@ public abstract class Editor extends JPanel implements ProjectElementListener {
return tfField;
}
public static JTextArea addTranslatableTextArea(JPanel pane, String label, String initialValue, boolean editable, final FieldUpdateListener listener) {
final JTextArea tfArea = addTextArea(pane, label, initialValue, editable, listener);
if (Workspace.activeWorkspace.settings.translatorLanguage.getCurrentValue() != null) {
final JLabel translateLinkLabel = new JLabel(getWeblateLabelLink(initialValue));
pane.add(translateLinkLabel, JideBoxLayout.FIX);
tfArea.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfArea.getText().replaceAll("\n", Matcher.quoteReplacement("\n"))));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
@Override
public void insertUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfArea.getText().replaceAll("\n", Matcher.quoteReplacement("\n"))));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
@Override
public void changedUpdate(DocumentEvent e) {
translateLinkLabel.setText(getWeblateLabelLink(tfArea.getText().replaceAll("\n", Matcher.quoteReplacement("\n"))));
translateLinkLabel.revalidate();
translateLinkLabel.repaint();
}
});
translateLinkLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
translateLinkLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
try {
Desktop.getDesktop().browse(new URI(getWeblateLabelURI(tfArea.getText().replaceAll("\n", Matcher.quoteReplacement("\n")))));
} catch (IOException e1) {
e1.printStackTrace();
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
}
}
});
}
return tfArea;
}
public static JTextArea addTextArea(JPanel pane, String label, String initialValue, boolean editable, final FieldUpdateListener listener) {
String text= initialValue == null ? "" : initialValue.replaceAll("\\n", "\n");

View File

@@ -6,7 +6,10 @@ import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
@@ -28,6 +31,9 @@ public class WorkspaceSettingsEditor extends JDialog {
JRadioButton useSystemDefaultImageViewerButton, useSystemDefaultImageEditorButton, useCustomImageEditorButton;
JTextField imageEditorCommandField;
JCheckBox translatorModeBox;
JComboBox<String> translatorLanguagesBox;
public WorkspaceSettingsEditor(WorkspaceSettings settings) {
@@ -46,6 +52,7 @@ public class WorkspaceSettingsEditor extends JDialog {
pane.add(getExternalToolsPane(), JideBoxLayout.FIX);
pane.add(getTranslatorModePane(), JideBoxLayout.FIX);
pane.add(new JPanel(), JideBoxLayout.VARY);
buttonPane.add(new JPanel(), JideBoxLayout.VARY);
@@ -147,6 +154,32 @@ public class WorkspaceSettingsEditor extends JDialog {
return pane;
}
public JPanel getTranslatorModePane() {
CollapsiblePanel pane = new CollapsiblePanel("Translator options");
pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS));
translatorModeBox = new JCheckBox("Activate translator mode");
pane.add(translatorModeBox, JideBoxLayout.FIX);
JPanel langPane = new JPanel();
langPane.setLayout(new JideBoxLayout(langPane, JideBoxLayout.LINE_AXIS));
langPane.add(new JLabel("Language code: "), JideBoxLayout.FIX);
translatorLanguagesBox = new JComboBox<String>(WorkspaceSettings.LANGUAGE_LIST);
langPane.add(translatorLanguagesBox);
pane.add(langPane, JideBoxLayout.FIX);
translatorModeBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
translatorLanguagesBox.setEnabled(translatorModeBox.isSelected());
}
});
pane.add(new JLabel("If your language isn't here, complain on the forums at https://andorstrail.com/"), JideBoxLayout.FIX);
return pane;
}
public void loadFromModel() {
//Tiled
useSystemDefaultMapEditorButton.setSelected(settings.useSystemDefaultMapEditor.getCurrentValue());
@@ -157,6 +190,14 @@ public class WorkspaceSettingsEditor extends JDialog {
useSystemDefaultImageEditorButton.setSelected(settings.useSystemDefaultImageEditor.getCurrentValue());
useCustomImageEditorButton.setSelected(!(settings.useSystemDefaultImageViewer.getCurrentValue() || settings.useSystemDefaultImageEditor.getCurrentValue()));
imageEditorCommandField.setText(settings.imageEditorCommand.getCurrentValue());
//Translator
if (settings.translatorLanguage.getCurrentValue() != null) {
translatorModeBox.setSelected(true);
translatorLanguagesBox.setSelectedItem(settings.translatorLanguage.getCurrentValue());
} else {
translatorModeBox.setSelected(false);
translatorLanguagesBox.setSelectedItem(null);
}
}
public void pushToModel() {
@@ -167,7 +208,12 @@ public class WorkspaceSettingsEditor extends JDialog {
settings.useSystemDefaultImageViewer.setCurrentValue(useSystemDefaultImageViewerButton.isSelected());
settings.useSystemDefaultImageEditor.setCurrentValue(useSystemDefaultImageEditorButton.isSelected());
settings.imageEditorCommand.setCurrentValue(imageEditorCommandField.getText());
//Translator
if (translatorModeBox.isSelected()) {
settings.translatorLanguage.setCurrentValue((String)translatorLanguagesBox.getSelectedItem());
} else {
settings.translatorLanguage.resetDefault();
}
settings.save();
}

View File

@@ -76,7 +76,7 @@ public class ActorConditionEditor extends JSONElementEditor {
acIcon = createButtonPane(pane, ac.getProject(), ac, ActorCondition.class, ac.getImage(), Spritesheet.Category.actorcondition, listener);
idField = addTextField(pane, "Internal ID: ", ac.id, ac.writable, listener);
nameField = addTextField(pane, "Display name: ", ac.display_name, ac.writable, listener);
nameField = addTranslatableTextField(pane, "Display name: ", ac.display_name, ac.writable, listener);
categoryBox = addEnumValueBox(pane, "Category: ", ActorCondition.ACCategory.values(), ac.category, ac.writable, listener);
positiveBox = addIntegerBasedCheckBox(pane, "Positive", ac.positive, ac.writable, listener);
stackingBox = addIntegerBasedCheckBox(pane, "Stacking", ac.stacking, ac.writable, listener);

View File

@@ -165,7 +165,7 @@ public class DialogueEditor extends JSONElementEditor {
createButtonPane(pane, dialogue.getProject(), dialogue, Dialogue.class, dialogue.getImage(), null, listener);
idField = addTextField(pane, "Internal ID: ", dialogue.id, dialogue.writable, listener);
messageField = addTextArea(pane, "Message: ", dialogue.message, dialogue.writable, listener);
messageField = addTranslatableTextArea(pane, "Message: ", dialogue.message, dialogue.writable, listener);
switchToNpcBox = addNPCBox(pane, dialogue.getProject(), "Switch active NPC to: ", dialogue.switch_to_npc, dialogue.writable, listener);
CollapsiblePanel rewards = new CollapsiblePanel("Reaching this phrase gives the following rewards: ");

View File

@@ -52,7 +52,7 @@ public class ItemCategoryEditor extends JSONElementEditor {
idField = addTextField(pane, "Internal ID: ", ic.id, ic.writable, listener);
nameField = addTextField(pane, "Display name: ", ic.name, ic.writable, listener);
nameField = addTranslatableTextField(pane, "Display name: ", ic.name, ic.writable, listener);
typeBox = addEnumValueBox(pane, "Action type: ", ItemCategory.ActionType.values(), ic.action_type, ic.writable, listener);
slotBox = addEnumValueBox(pane, "Inventory slot: ", ItemCategory.InventorySlot.values(), ic.slot, ic.writable, listener);
sizeBox = addEnumValueBox(pane, "Item size: ", ItemCategory.Size.values(), ic.size, ic.writable, listener);

View File

@@ -149,8 +149,8 @@ public class ItemEditor extends JSONElementEditor {
itemIcon = createButtonPane(pane, item.getProject(), item, Item.class, item.getImage(), Spritesheet.Category.item, listener);
idField = addTextField(pane, "Internal ID: ", item.id, item.writable, listener);
nameField = addTextField(pane, "Display name: ", item.name, item.writable, listener);
descriptionField = addTextField(pane, "Description: ", item.description, item.writable, listener);
nameField = addTranslatableTextField(pane, "Display name: ", item.name, item.writable, listener);
descriptionField = addTranslatableTextField(pane, "Description: ", item.description, item.writable, listener);
typeBox = addEnumValueBox(pane, "Type: ", Item.DisplayType.values(), item.display_type, item.writable, listener);
manualPriceBox = addIntegerBasedCheckBox(pane, "Has manual price", item.has_manual_price, item.writable, listener);
baseManualPrice = item.base_market_cost;

View File

@@ -171,7 +171,7 @@ public class NPCEditor extends JSONElementEditor {
npcIcon = createButtonPane(pane, npc.getProject(), npc, NPC.class, npc.getImage(), Spritesheet.Category.monster, listener);
idField = addTextField(pane, "Internal ID: ", npc.id, npc.writable, listener);
nameField = addTextField(pane, "Display name: ", npc.name, npc.writable, listener);
nameField = addTranslatableTextField(pane, "Display name: ", npc.name, npc.writable, listener);
spawnGroupField = addTextField(pane, "Spawn group ID: ", npc.spawngroup_id, npc.writable, listener);
experienceField = addIntegerField(pane, "Experience reward: ", npc.getMonsterExperience(), false, false, listener);
dialogueBox = addDialogueBox(pane, npc.getProject(), "Initial phrase: ", npc.dialogue, npc.writable, listener);

View File

@@ -69,7 +69,7 @@ public class QuestEditor extends JSONElementEditor {
idField = addTextField(pane, "Internal ID: ", quest.id, quest.writable, listener);
nameField = addTextField(pane, "Quest Name: ", quest.name, quest.writable, listener);
nameField = addTranslatableTextField(pane, "Quest Name: ", quest.name, quest.writable, listener);
visibleBox = addIntegerBasedCheckBox(pane, "Visible in quest log", quest.visible_in_log, quest.writable, listener);
JPanel stagesPane = new JPanel();

View File

@@ -0,0 +1,60 @@
package com.gpl.rpg.atcontentstudio.utils;
import java.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.Map;
import com.zackehh.siphash.SipHash;
import com.zackehh.siphash.SipHashResult;
public class HashUtils {
private static final String WEBLATE_SIPASH_KEY = "Weblate Sip Hash";
private static final Map<String, SipHash> HASHER_CACHE = new LinkedHashMap<String, SipHash>();
public static String weblateHash(String str, String ctx) {
byte[] data = null;
if (str != null) {
byte[] strBytes;
try {
strBytes = str.getBytes("UTF-8");
byte[] ctxBytes = ctx.getBytes("UTF-8");
data = new byte[strBytes.length + ctxBytes.length];
System.arraycopy(strBytes, 0, data, 0, strBytes.length);
System.arraycopy(ctxBytes, 0, data, strBytes.length, ctxBytes.length);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
try {
data = ctx.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return siphash(WEBLATE_SIPASH_KEY, data);
}
private static String siphash(String key, byte[] data) {
SipHash hasher = HASHER_CACHE.get(key);
if (hasher == null) {
hasher= new SipHash("Weblate Sip Hash".getBytes());
HASHER_CACHE.put(key, hasher);
}
if (data != null) {
SipHashResult result = hasher.hash(data);
return result.getHex();
}
return null;
}
}