Add VIN result type

This commit is contained in:
Sean Owen 2014-01-18 19:42:06 +00:00
parent 3f6ee7f789
commit 9983ccc428
5 changed files with 380 additions and 1 deletions

View file

@ -35,5 +35,6 @@ public enum ParsedResultType {
CALENDAR,
WIFI,
ISBN,
VIN,
}

View file

@ -59,6 +59,7 @@ public abstract class ResultParser {
new ISBNResultParser(),
new ProductResultParser(),
new ExpandedProductResultParser(),
new VINResultParser(),
};
private static final Pattern DIGITS = Pattern.compile("\\d*");

View file

@ -0,0 +1,104 @@
/*
* Copyright 2014 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
public final class VINParsedResult extends ParsedResult {
private final String vin;
private final String worldManufacturerID;
private final String vehicleDescriptorSection;
private final String vehicleIdentifierSection;
private final String countryCode;
private final String vehicleAttributes;
private final int modelYear;
private final char plantCode;
private final String sequentialNumber;
public VINParsedResult(String vin,
String worldManufacturerID,
String vehicleDescriptorSection,
String vehicleIdentifierSection,
String countryCode,
String vehicleAttributes,
int modelYear,
char plantCode,
String sequentialNumber) {
super(ParsedResultType.VIN);
this.vin = vin;
this.worldManufacturerID = worldManufacturerID;
this.vehicleDescriptorSection = vehicleDescriptorSection;
this.vehicleIdentifierSection = vehicleIdentifierSection;
this.countryCode = countryCode;
this.vehicleAttributes = vehicleAttributes;
this.modelYear = modelYear;
this.plantCode = plantCode;
this.sequentialNumber = sequentialNumber;
}
public String getVIN() {
return vin;
}
public String getWorldManufacturerID() {
return worldManufacturerID;
}
public String getVehicleDescriptorSection() {
return vehicleDescriptorSection;
}
public String getVehicleIdentifierSection() {
return vehicleIdentifierSection;
}
public String getCountryCode() {
return countryCode;
}
public String getVehicleAttributes() {
return vehicleAttributes;
}
public int getModelYear() {
return modelYear;
}
public char getPlantCode() {
return plantCode;
}
public String getSequentialNumber() {
return sequentialNumber;
}
@Override
public String getDisplayResult() {
StringBuilder result = new StringBuilder(50);
result.append(worldManufacturerID).append(' ');
result.append(vehicleDescriptorSection).append(' ');
result.append(vehicleIdentifierSection).append('\n');
if (countryCode != null) {
result.append(countryCode).append(' ');
}
result.append(modelYear).append(' ');
result.append(plantCode).append(' ');
result.append(sequentialNumber).append('\n');
return result.toString();
}
}

View file

@ -0,0 +1,206 @@
/*
* Copyright 2014 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import java.util.regex.Pattern;
/**
* Detects a result that is likely a vehicle identification number.
*
* @author Sean Owen
*/
public final class VINResultParser extends ResultParser {
private static final Pattern IOQ = Pattern.compile("[IOQ]");
private static final Pattern AZ09 = Pattern.compile("[A-Z0-9]{17}");
@Override
public VINParsedResult parse(Result result) {
if (result.getBarcodeFormat() != BarcodeFormat.CODE_39) {
return null;
}
String rawText = result.getText();
rawText = IOQ.matcher(rawText).replaceAll("").trim();
if (!AZ09.matcher(rawText).matches()) {
return null;
}
if (!checkChecksum(rawText)) {
return null;
}
String wmi = rawText.substring(0, 3);
return new VINParsedResult(rawText,
wmi,
rawText.substring(3, 9),
rawText.substring(9, 17),
countryCode(wmi),
rawText.substring(3, 8),
modelYear(rawText.charAt(9)),
rawText.charAt(10),
rawText.substring(11));
}
private static boolean checkChecksum(CharSequence vin) {
int sum = 0;
for (int i = 0; i < vin.length(); i++) {
sum += vinPositionWeight(i + 1) * vinCharValue(vin.charAt(i));
}
char checkChar = vin.charAt(8);
char expectedCheckChar = checkChar(sum % 11);
return checkChar == expectedCheckChar;
}
private static int vinCharValue(char c) {
if (c >= 'A' && c <= 'I') {
return (c - 'A') + 1;
}
if (c >= 'J' && c <= 'R') {
return (c - 'J') + 1;
}
if (c >= 'S' && c <= 'Z') {
return (c - 'S') + 2;
}
if (c >= '0' && c <= '9') {
return c - '0';
}
throw new IllegalArgumentException();
}
private static int vinPositionWeight(int position) {
if (position >= 1 && position <= 7) {
return 9 - position;
}
if (position == 8) {
return 10;
}
if (position == 9) {
return 0;
}
if (position >= 10 && position <= 17) {
return 19 - position;
}
throw new IllegalArgumentException();
}
private static char checkChar(int remainder) {
if (remainder < 10) {
return (char) ('0' + remainder);
}
if (remainder == 10) {
return 'X';
}
throw new IllegalArgumentException();
}
private static int modelYear(char c) {
if (c >= 'E' && c <= 'H') {
return (c - 'E') + 1984;
}
if (c >= 'J' && c <= 'N') {
return (c - 'J') + 1988;
}
if (c == 'P') {
return 1993;
}
if (c >= 'R' && c <= 'T') {
return (c - 'R') + 1994;
}
if (c >= 'V' && c <= 'Y') {
return (c - 'V') + 1997;
}
if (c >= '1' && c <= '9') {
return (c - '1') + 2001;
}
if (c >= 'A' && c <= 'D') {
return (c - 'A') + 2010;
}
throw new IllegalArgumentException();
}
private static String countryCode(CharSequence wmi) {
char c1 = wmi.charAt(0);
char c2 = wmi.charAt(1);
switch (c1) {
case '1':
case '4':
case '5':
return "US";
case '2':
return "CA";
case '3':
if (c2 >= 'A' && c2 <= 'W') {
return "MX";
}
break;
case '9':
if ((c2 >= 'A' && c2 <= 'E') || (c2 >= '3' && c2 <= '9')) {
return "BR";
}
break;
case 'J':
if (c2 >= 'A' && c2 <= 'T') {
return "JP";
}
break;
case 'K':
if (c2 >= 'L' && c2 <= 'R') {
return "KO";
}
break;
case 'L':
return "CN";
case 'M':
if (c2 >= 'A' && c2 <= 'E') {
return "IN";
}
break;
case 'S':
if (c2 >= 'A' && c2 <= 'M') {
return "UK";
}
if (c2 >= 'N' && c2 <= 'T') {
return "DE";
}
break;
case 'V':
if (c2 >= 'F' && c2 <= 'R') {
return "FR";
}
if (c2 >= 'S' && c2 <= 'W') {
return "ES";
}
break;
case 'W':
return "DE";
case 'X':
if (c2 == '0' || (c2 >= '3' && c2 <= '9')) {
return "RU";
}
break;
case 'Z':
if (c2 >= 'A' && c2 <= 'R') {
return "IT";
}
break;
}
return null;
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2014 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.result;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import org.junit.Assert;
import org.junit.Test;
public final class VINParsedResultTestCase extends Assert {
@Test
public void testNotVIN() {
Result fakeResult = new Result("1M8GDM9A1KP042788", null, null, BarcodeFormat.CODE_39);
ParsedResult result = ResultParser.parseResult(fakeResult);
assertEquals(ParsedResultType.TEXT, result.getType());
fakeResult = new Result("1M8GDM9AXKP042788", null, null, BarcodeFormat.CODE_128);
result = ResultParser.parseResult(fakeResult);
assertEquals(ParsedResultType.TEXT, result.getType());
}
@Test
public void testVIN() {
doTest("1M8GDM9AXKP042788", "1M8", "GDM9AX", "KP042788", "US", "GDM9A", 1989, 'P', "042788");
doTest("I1M8GDM9AXKP042788", "1M8", "GDM9AX", "KP042788", "US", "GDM9A", 1989, 'P', "042788");
doTest("LJCPCBLCX11000237", "LJC", "PCBLCX", "11000237", "CN", "PCBLC", 2001, '1', "000237");
}
private static void doTest(String contents,
String wmi,
String vds,
String vis,
String country,
String attributes,
int year,
char plant,
String sequential) {
Result fakeResult = new Result(contents, null, null, BarcodeFormat.CODE_39);
ParsedResult result = ResultParser.parseResult(fakeResult);
assertSame(ParsedResultType.VIN, result.getType());
VINParsedResult vinResult = (VINParsedResult) result;
assertEquals(wmi, vinResult.getWorldManufacturerID());
assertEquals(vds, vinResult.getVehicleDescriptorSection());
assertEquals(vis, vinResult.getVehicleIdentifierSection());
assertEquals(country, vinResult.getCountryCode());
assertEquals(attributes, vinResult.getVehicleAttributes());
assertEquals(year, vinResult.getModelYear());
assertEquals(plant, vinResult.getPlantCode());
assertEquals(sequential, vinResult.getSequentialNumber());
}
}