Skip to content

Commit

Permalink
Final working solution
Browse files Browse the repository at this point in the history
  • Loading branch information
lukecollier committed Jul 4, 2017
0 parents commit 0de37d1
Show file tree
Hide file tree
Showing 1,273 changed files with 93,097 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
logs
target
/.idea
/.idea_modules
/.classpath
/.project
/.settings
/RUNNING_PID
.DS_Store
.idea/*
target/*
13 changes: 13 additions & 0 deletions .sbtserver/connections/master.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
New log file opened at Tue Jul 04 12:08:48 BST 2017 by [email protected]
Taking next connection to: 50385
New client attempting to connect on port: 50386-50385
Address = /192.168.1.107:50385
This client on port 50386 has uuid a4196135-c1a3-4be7-aa32-500bd209e80e configName play-fork-run humanReadableName Play Fork Run protocolVersion 1 featureTags Vector()
Connected Clients: play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e
Taking next connection to: 50385
Checking to see if clients are empty...
No non-daemon clients connected after 3 min. Shutting down.
Server socket thread exiting.
0 clients open, will close them...
All client sockets have been closed.
Closing the [email protected] logs at Tue Jul 04 12:11:58 BST 2017, goodbye.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
New log file opened at Tue Jul 04 12:08:58 BST 2017 by [email protected]
Sending msg to client play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e: DetachedLogEvent(LogMessage(debug,sbt server socket logs are in: /Users/Joker/projects/rentalcars-rest-api/.sbtserver/connections/master.log))
Sending msg to client play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e: DetachedLogEvent(LogMessage(debug,sbt client logs are in: /Users/Joker/projects/rentalcars-rest-api/.sbtserver/connections/play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e.log))
Sending msg to client play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e: DetachedLogEvent(LogMessage(debug,sbt general server logs are in: /Users/Joker/projects/rentalcars-rest-api/.sbtserver/master.log))
Dropping message DetachedLogEvent(LogMessage(debug,sbt general server logs are in: /Users/Joker/projects/rentalcars-rest-api/.sbtserver/master.log)) because client play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e of an exception serializing and sending: java.net.SocketException: Broken pipe
Dropping message DetachedLogEvent(LogMessage(debug,sbt client logs are in: /Users/Joker/projects/rentalcars-rest-api/.sbtserver/connections/play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e.log)) because client play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e of an exception serializing and sending: java.net.SocketException: Broken pipe
stack trace sending message was
java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109)
at java.net.SocketOutputStream.write(SocketOutputStream.java:153)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
at java.io.DataOutputStream.flush(DataOutputStream.java:123)
at sbt.impl.ipc.Peer.send(IPC.scala:79)
at sbt.impl.ipc.Peer.send(IPC.scala:83)
at sbt.impl.ipc.Peer.sendString(IPC.scala:106)
at sbt.impl.ipc.Peer.sendJson(IPC.scala:119)
at sbt.server.SbtClientHandler$$anonfun$send$1.apply$mcV$sp(SbtClientHandler.scala:128)
at sbt.server.SbtClientHandler.wrappedSend(SbtClientHandler.scala:113)
at sbt.server.SbtClientHandler.send(SbtClientHandler.scala:128)
at sbt.server.SbtServerSocketHandler$$anon$1.run(SbtServerSocketHandler.scala:94)
stack trace sending message was
java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109)
at java.net.SocketOutputStream.write(SocketOutputStream.java:153)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
at java.io.DataOutputStream.flush(DataOutputStream.java:123)
at sbt.impl.ipc.Peer.send(IPC.scala:79)
at sbt.impl.ipc.Peer.send(IPC.scala:83)
at sbt.impl.ipc.Peer.sendString(IPC.scala:106)
at sbt.impl.ipc.Peer.sendJson(IPC.scala:119)
at sbt.server.SbtClientHandler$$anonfun$send$1.apply$mcV$sp(SbtClientHandler.scala:128)
at sbt.server.SbtClientHandler.wrappedSend(SbtClientHandler.scala:113)
at sbt.server.SbtClientHandler.send(SbtClientHandler.scala:128)
at sbt.server.SbtClientHandler$clientThread$.run(SbtClientHandler.scala:42)
Reading next message from client.
Client play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e closed, java.net.SocketException: socket is closed, shutting down
Dropping message (0,ReceivedResponse()) to dead client play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e
Client play-fork-run-a4196135-c1a3-4be7-aa32-500bd209e80e thread exiting.
25,088 changes: 25,088 additions & 0 deletions .sbtserver/master.log

Large diffs are not rendered by default.

4,957 changes: 4,957 additions & 0 deletions .sbtserver/previous.log

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: scala
scala:
- 2.11.8
jdk:
- oraclejdk8
cache:
directories:
- "$HOME/.ivy2/cache"
before_cache:
- rm -rf $HOME/.ivy2/cache/com.typesafe.play/*
- rm -rf $HOME/.ivy2/cache/scala_*/sbt_*/com.typesafe.play/*
- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print0 | xargs -n10 -0 rm
8 changes: 8 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
This software is licensed under the Apache 2 license, quoted below.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project 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.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Rentalcars Rest API for Technical Test Intake 2017
[![Build Status](https://travis-ci.org/LukeCollier/rentalcars-rest-api.svg?branch=master)](https://travis-ci.org/LukeCollier/rentalcars-rest-api)

## Part 1 Console Application
To use the console application goto the project directory and type `java -cp target/rentalcar-technical-test-1.0-SNAPSHOT-jar-with-dependencies com.rentalcars.app.ConsoleApplication` in the console of your choice.
![Proof of the working console application](images/console-proof.png?raw=true)

## Part 2 API
Either run the bash script at `/target/universal/com-rentalcars-rest-api-1.0-SNAPSHOT/bin/com-rentalcars-rest-api`
or use the .bat file at `/target/universal/com-rentalcars-rest-api-1.0-SNAPSHOT/bin/com-rentalcars-rest-api.bat`
The server will attempt to run on port 9000 of the machine (localhost:9000).

Once the localhost is running there are 4 different url's for each of the tasks
1. http://localhost:9000/car/sort/price - get's the vehicle list in price order
![Sort price working](images/sort-price.png?raw=true)
2. http://localhost:9000/car/summary - get's the vehicle list summary
![Sort price working](images/summary.png?raw=true)
3. http://localhost:9000/car/sort/rating - get's the vehicle list in rating and
vehicle type order
![Sort price working](images/sort-rating.png?raw=true)
4. http://localhost:9000/car/sort/score - get's the vehicle list by score order
![Sort price working](images/sort-score.png?raw=true)
Binary file added app/.DS_Store
Binary file not shown.
136 changes: 136 additions & 0 deletions app/com/rentalcars/CarInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package com.rentalcars;

import com.rentalcars.models.Car;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;


public class CarInfo {
// Handy variables with the path's to the relevant resource files
private final static String CAR_TYPES_PATH = "/car-types.json";
private final static String DOOR_TYPES_PATH = "/door-types.json";
private final static String TRANSMISSION_PATH = "/transmission-types.json";
private final static String FUEL_AIR_CON_PATH = "/fuel-air-con-types.json";

// List of positions for
private final static int CARS_POS = 0;
private final static int DOORS_POS = 1;
private final static int TRANS_POS = 2;
private final static int FUEL_POS = 3;

private final LetterToValue carTypes;
private final LetterToValue doorTypes;
private final LetterToValue transmissionTypes;
private final LetterToValue fuelAirConTypes;

/**
* Initializes the different LetterToValue types by using try-with-resources that will automatically close the
* stream
* @throws IOException
*/
public CarInfo() throws IOException {
try(InputStream inputStreamCarTypes = getClass().getResourceAsStream(CAR_TYPES_PATH)){
carTypes = LetterToValue.readSpecification(inputStreamCarTypes);
}

try(InputStream inputStreamDoorTypes = getClass().getResourceAsStream(DOOR_TYPES_PATH))
{
doorTypes = LetterToValue.readSpecification(inputStreamDoorTypes);
}

try(InputStream inputStreamTransmissionTypes = getClass().getResourceAsStream(TRANSMISSION_PATH)){
transmissionTypes = LetterToValue.readSpecification(inputStreamTransmissionTypes);
}

try(InputStream inputStreamFuelAirCon = getClass().getResourceAsStream(FUEL_AIR_CON_PATH))
{
fuelAirConTypes = LetterToValue.readSpecification(inputStreamFuelAirCon);
}
}

/**
* Task one of the technical test, get's the name and price ordered by the price in ascending order.
* @param carList the car list to order and get the string of
* @return
*/
public String formatPriceOrder(List<Car> carList) {
return carList.stream()
.sorted(Comparator.comparing(Car::getPrice))
.map(car -> String.format("{%s} - {%s}",
car.getName(),car.getPrice().setScale(2, BigDecimal.ROUND_CEILING)))
.collect(Collectors.joining("\n"));
}

/**
* Task two of the technical test, get's the name, sipp code, sipp code meanings output in the order it was received
* @param carList the car list to get the string of
* @return
* @throws IOException
*/
public String formatCarSummary(List<Car> carList) throws IOException {
return carList.stream()
.map(car -> String.format("{%s} - {%s} - {%s} - {%s} - {%s} - {%s} - {%s}",
car.getName(),
car.getSipp(),
carTypes.getValueFromLetter(car.getSipp().charAt(CARS_POS)),
doorTypes.getValueFromLetter(car.getSipp().charAt(DOORS_POS)),
transmissionTypes.getValueFromLetter(car.getSipp().charAt(TRANS_POS)),
fuelAirConTypes.getValueFromLetter(car.getSipp().charAt(FUEL_POS)).split("/")[0],
fuelAirConTypes.getValueFromLetter(car.getSipp().charAt(FUEL_POS)).split("/")[1]
))
.collect(Collectors.joining("\n"));
}

public String formatCarTypeRatingOrder(List<Car> carList) {
return carList.stream()
.sorted((car1, car2) -> {
Float rating1 = car1.getRating();
Float rating2 = car2.getRating();
return rating2.compareTo(rating1);
})
.sorted((car1, car2) -> {
String carType1 = carTypes.getValueFromLetter(car1.getSipp().charAt(CARS_POS));
String carType2 = carTypes.getValueFromLetter(car2.getSipp().charAt(DOORS_POS));
return carType1.compareTo(carType2);
})
.map(car -> String.format("{%s} - {%s} - {%s} - {%s}",
car.getName(),
carTypes.getValueFromLetter(car.getSipp().charAt(0)),
car.getSupplier(),
car.getRating()))
.collect(Collectors.joining("\n"));
}

public String formatCarScoreOrder(List<Car> carList) {
return carList.stream()
.sorted((car1, car2) -> {
Float score1 = car1.getRating() + getScoreFromSipp(car1.getSipp());
Float score2 = car2.getRating() + getScoreFromSipp(car2.getSipp());
return score2.compareTo(score1);
})
.map((Car car) -> {
int score = getScoreFromSipp(car.getSipp());
return String.format("{%s} - {%d} - {%.1f} - {%.1f}",
car.getName(), score,car.getRating(),car.getRating()+score);

})
.collect(Collectors.joining("\n"));
}

/**
* Takes the last two characters of the SIPP and finds their combined score
* @param sipp a string that is the SiPP code to use
* @return an integer value representing the total score of the SiPP
* @throws IOException if an error occurs with the input stream
*/
private int getScoreFromSipp(String sipp) {
return transmissionTypes.getScoreFromLetter(sipp.charAt(TRANS_POS))
+ fuelAirConTypes.getScoreFromLetter(sipp.charAt(FUEL_POS));

}
}
42 changes: 42 additions & 0 deletions app/com/rentalcars/ConsoleApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.rentalcars;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.rentalcars.models.Car;
import com.rentalcars.models.VehiclesResponse;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.List;

public class ConsoleApplication {

private final static String TARGET_WEBSITE_PATH = "http://www.rentalcars.com/js/vehicles.json";

public static void main(String[] args) throws IOException {

// The website url to get the data from
URL websiteUrl = new URL(TARGET_WEBSITE_PATH);
BufferedReader in = new BufferedReader(new InputStreamReader(websiteUrl.openStream()));

// Get's the response
ObjectMapper mapper = new ObjectMapper();

List<Car> vehicleList = mapper.readValue(in, VehiclesResponse.class).Search.VehicleList;

CarInfo carInfo = new CarInfo();

System.out.println("Part 1 Task 1: ");
System.out.println(carInfo.formatPriceOrder(vehicleList));

System.out.println("Part 1 Task 2: ");
System.out.println(carInfo.formatCarSummary(vehicleList));

System.out.println("Part 1 Task 3: ");
System.out.println(carInfo.formatCarTypeRatingOrder(vehicleList));

System.out.println("Part 1 Task 4: ");
System.out.println(carInfo.formatCarScoreOrder(vehicleList));
}
}
103 changes: 103 additions & 0 deletions app/com/rentalcars/LetterToValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.rentalcars;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rentalcars.models.SpecType;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Specifications are a map of characters to an string
*/
public class LetterToValue {
private final Map<Character, SpecificationWrapper> specificationData;

/**
* Static constructor
* @param data set the data to equal this
*/
private LetterToValue(Map<Character, SpecificationWrapper> data) {
specificationData = data;
}

/**
* Read a specification from input stream
* @param inputStream the input stream to read the information from
* @return an LetterToValue object with information from the input stream
* @throws IOException thrown if an error occurs with the input stream
*/
public static LetterToValue readSpecification(InputStream inputStream) throws IOException {
ObjectMapper mapper = new ObjectMapper();
List<SpecType> dataSpecList = mapper.readValue(inputStream, new TypeReference<List<SpecType>>(){});

final Map<Character, SpecificationWrapper> specData = new HashMap<>();

dataSpecList.forEach(specType -> specData.put(specType.getLetter(),new SpecificationWrapper(specType)));

return new LetterToValue(specData);
}

/**
* Get's the corresponding value from a character
* @param letter the character to get the corresponding string value from
* @return a string representation of the characters code
*/
public String getValueFromLetter(char letter) {
if (specificationData.containsKey(letter)) {
return specificationData.get(letter).getValue();
} else {
return "not found";
}
}

/**
* Get's the score representation from the character
* @param letter the character code to check against
* @return the score of the characters code
*/
public int getScoreFromLetter(char letter) {
if (!specificationData.containsKey(letter)) {
return 0;
} else {
return specificationData.get(letter).getScore();
}
}

/**
* A data wrapper for the SpecType taken from the json file
*/
static class SpecificationWrapper {
private String value;
private int score = 0;

/**
* A default constructor that converts a SpecType into SpecificationWrapper
* @param type the specification type
*/
SpecificationWrapper(SpecType type) {
this.value = type.getValue();
this.score = type.getScore();
}

/**
* Get's the value of the spec data
* @return an string representing the value
*/
String getValue() {
return value;
}

/**
* Get's the score for the spec data
* @return an integer representing the score
*/
int getScore() {
return score;
}
}

}
Loading

0 comments on commit 0de37d1

Please sign in to comment.