Read txt file into array and assign to variables - java

So essentially what I need to do is make this while loop run through the txt file and store it in an array to be stored inside the instance variables
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
/**
* <insert class description here>
*
* #author Chris Crosby
*
*/
public class TrackInput
{
private Scanner keyboard = new Scanner(System.in);
public int readTrackData(Railroad[] Reservations) {
final String FILE_NAME = "TrackData.txt";
int size =0;
Scanner input = null;
try {
input= new Scanner(new File(FILE_NAME));
}
catch(FileNotFoundException e) {
System.out.println("Unable to open file " + FILE_NAME + ".");
}
String passengerName="";
String routeNumber="";
String departureDate="";
String departureTrack="";
String arrivalTrack="";
String departureTime="";
String arrivalTime="";
String seat="";
String returnRouteNumber="";
String ReturnDate="";
while (input.hasNext()&& size<Reservations.length) {
}
return size;
}
}
here's the txt file that the loop is reading through
Carl Foreman
1234
UA1235
06/23/2014
ORD
LGA
4:00 PM
7:15 PM
23A
UA673
07/12/2014
LGA
ORD
10:00 AM
11:25 AM
8A
Jennifer Foreman
1235
UA1235
06/23/2014
ORD
LGA
4:00 PM
7:15 PM
23B
UA673
07/12/2014
LGA
ORD
10:00 AM
11:25 AM
8B
Jane Anderson
4577
UA317
08/04/2014
ORD
SFO
8:10 AM
10:45 AM
11C
UA728
08/14/2014
SFO
ORD
12:52 PM
7:03 PM
10D
Jason Anderson
4578
TrackData.txt format
passenger name – include first and last name in one variable
reservation number
departure route number
departure date
departure track
arrival track
departure time
arrival time
seat
return route number
return date
departure track
arrival track
departure time
arrival time
seat
here's a similar method I had to write for a previous assignment
public int readInventory(Automobile[] inventory)
{
final String FILENAME = "inventory.csv";
int size = 0;
Scanner input = null;
try
{
input = new Scanner(new File(FILENAME));
}
catch (FileNotFoundException e)
{
System.out.println("Unable to open file " + FILENAME + ".");
}
// read header line and discard first line
String line = input.nextLine();
// create vars
int year = 0;
String make = "";
String model = "";
double price = 0.0;
String condition = "";
int rating = 0;
String status = "";
String vin = "";
// Customer vars
String firstName = "";
String lastName = "";
String streetAddress = "";
String city = "";
String state = "";
String zip = "";
String email = "";
String phone = "";
while (input.hasNext() && size < inventory.length)
{
line = input.nextLine();
String[] record = line.split(",");
year = Integer.parseInt(record[0]);
make = record[1];
model = record[2];
// If use this version, comment out the following if statements
if (!(record[3].equals("")))
{
price = Double.parseDouble(record[3]);
}
else
{
price = 0;
}
condition = record[4];
if (!(record[5].equals("")))
{
rating = Integer.parseInt(record[5]);
}
else
{
rating = 0;
}
status = record[6];
vin = record[7];
// this is where the records differ
// they either don't have buyer information or some do
if (record.length > 8)
{
if (!(record[8].equals("")))
firstName = record[8];
else
firstName = "";
if (!(record[9].equals("")))
lastName = record[9];
else
lastName = "";
if (!(record[10].equals("")))
streetAddress = record[10];
else
streetAddress = "";
if (!(record[11].equals("")))
city = record[11];
else
city = "";
if (!(record[12].equals("")))
state = record[12];
else
state = "";
if (!(record[13].equals("")))
zip = record[13];
else
zip = "";
if (!(record[14].equals("")))
email = record[14];
else
email = "";
if (!(record[15].equals("")))
phone = record[15];
else
phone = "";
}
// changes to integrate Customer class go below
Customer tempCustomer = new Customer(firstName,lastName, city, state, email, phone,zip,streetAddress);
Automobile tempAutomobile = new Automobile( year, make, model, price,
condition, rating, status, vin, tempCustomer);
inventory[size] = tempAutomobile;
size++;
} // end of while loop
input.close();
return size;
}
not sure how to make it work for this program since this is multilined and the previous was a single line separated by commas hence the line.split

Hmmm... I am not sure i should answer this since it is not that hard and i am sure you can solve it yourself. Sadly i dont have enough reputation to just comment (that is why i am here in the first place).
I will just drop some hints to help you:
1) You know how to read line by line. You can choose when to start, stop and even skip lines. Sadly you can only move forward but that is enough for this problem.
2) The data file follows a constant pattern. This means this is the easy to handle data.
3) You know the pattern beforehand. Pst, it is the OP!
I think that is it. Now you should just pay attention and wait for the AHA! moment.

package edu.ilstu;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
/**
* <insert class description here>
*
* #author Chris Crosby
*
*/
public class TrackInput
{
public final String TRANSACTION_FILE = "Transactions.txt";
public final String DATA_FILE = "RailroadData.txt";
public TrackInput() {
}
Scanner transaction = null;
public Scanner readTransactions() {
try {
transaction= new Scanner(new File(TRANSACTION_FILE));
}
catch(FileNotFoundException e){
System.out.println("Unable to open file" + TRANSACTION_FILE);
System.exit(1);
}
return transaction;
}
public char readTransactionCode() {
char choice = transaction.nextLine().charAt(0);
return choice;
}
public int readRailroadData(Reservation [] reservations) {
Scanner data;
int count = 0;
try {
data = new Scanner(new File(DATA_FILE));
while(data.hasNext()) {
Reservation reservation = readReservationData(data);
reservations [count] = reservation;
count++;
}
}
catch(FileNotFoundException e){
System.out.println("Unable to open file" + DATA_FILE);
System.exit(1);
}
return count;
}
public Reservation readReservationData(Scanner input) {
String name = input.nextLine();
String departureReservationNumber=input.nextLine();
String departureRouteNumber = input.nextLine();
String departureRouteDate = input.nextLine();
String departureTrack = input.nextLine();
String departureArrivalTrack = input.nextLine();
String departureTime = input.nextLine();
String departureArrivalTime = input.nextLine();
String departureSeatAssignment = input.nextLine();
String returnRoute = input.nextLine();
String returnDate = input.nextLine();
String returnDepartureTrack = input.nextLine();
String returnArrivalTrack = input.nextLine();
String returnDepartureTime = input.nextLine();
String returnArrivalTime = input.nextLine();
String returnSeatAssignment = input.nextLine();
Route departureRoute = new Route(departureRouteNumber, departureRouteDate, departureTrack, departureArrivalTrack,departureTime, departureArrivalTime, departureSeatAssignment);
Route arrivalRoute= new Route(returnRoute, returnDate, returnDepartureTrack, returnArrivalTrack,returnDepartureTime,returnArrivalTime,returnSeatAssignment);
Reservation reservation = new Reservation(name,departureReservationNumber, departureRoute, arrivalRoute);
return reservation;
}
public int addReservationToList(Reservation[] reservationList,Reservation reservation, int size) {
reservationList[size] = reservation;
size++;
return size;
}
}

Related

Is there a way to collect data in multiple excel files?

there are 12 excel files that contain different products sales data, I try to sum up each month and entire year's sales using java but I can't add up sales for the whole year. Is there a way to do that? Thanks for your help.
public class Main {
public static void listOfSales(String fileName,String month){
List<PriceList> list = readsListFroExcel(fileName);
int sumOfInYearSale = 0;
int sumOfInOnlineSale =0;
int sumOfTotalMonthlySale = 0;
for (PriceList x : list){
sumOfInYearSale = sumOfInYearSale + x.getTotalPhysicalSale();
sumOfInOnlineSale = sumOfInYearSale + x.getTotalOnlineSale();
}
System.out.println(month +" Physical Sales : "+sumOfInYearSale);
System.out.println(month+ " Online Sales :" +sumOfInOnlineSale);
}
private static List<PriceList> readsListFroExcel(String fileName) {
List<PriceList> list = new ArrayList<>();
Path pathTofile = Paths.get(fileName);
try (BufferedReader br = Files.newBufferedReader(pathTofile)){
String headerLine = br.readLine();
String line = br.readLine();
while (line!=null){
String[] attributes = line.split(",");
PriceList listOfPrice = createList(attributes);
list.add(listOfPrice);
line = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
}

NoSuchElement Exception in Thread in TextFile Program

Hi. I'm having the issue in an error of exception. I don't know what is wrong. But please help me fix this. I'm trying to store data from the file to ArrayList and display the data in the ArrayList. Here, I attached my code and data Code and data source.
the NoSuchElementException appears because you are calling input.nextToken(); while input doesn't have any token.It's due to the last empty line of your file listbook.txt. By deleting this line, the exception shouldn't appear.
A proper manner could be to ensure that you have sufficient tokens to retrieve all your fields for a given line.
public class TextFile
{
public static void main(String[] args)
{
try
{
FileReader read = new FileReader("C:\\Users\\ogawi\\Downloads\\listbook.txt");
BufferedReader bf = new BufferedReader(read);
Book B = new Book();
ArrayList<Book> bookList = new ArrayList<Book>();
String data = null;
StringTokenizer input = null;
while((data = bf.readLine()) != null)
{
input = new StringTokenizer(data,";");
//we ensure that we have enough tokens to retrieve all fields
if(input.countTokens() == 6)
{
String title = input.nextToken();
String author = input.nextToken();
String publisher = input.nextToken();
String genre = input.nextToken();
int year = Integer.parseInt(input.nextToken());
int page = Integer.parseInt(input.nextToken());
B = new Book(title, author, publisher, genre, year, page);
bookList.add(B);
}
}
//This part of code has been moved outside the while loop
//to avoid to print the total content of the array each time
//an element is added
int count=0;
for(int i=0;i<bookList.size();i++)
{
B = (Book)bookList.get(i);
System.out.println(B.toString());
System.out.println("=============================");
count++;
}
System.out.println("Number of Books: " + count);
bf.close();
}
catch(FileNotFoundException fnf)
{ System.out.println(fnf.getMessage());}
catch(EOFException eof)
{ System.out.println(eof.getMessage());}
catch(IOException io)
{ System.out.println(io.getMessage());}
finally
{ System.out.println("System end here..TQ!!");}
}
}
This issue is due to the extra line without book information:
You can see the line 31 and 32 in above figure.
To solve this issue you can add one if condition data.contains(";") . Text file has ; delimiter if we check the condition if given line has ; delimiter then it won't cause an issue.
while ((data = bf.readLine()) != null) {
if (data.contains(";")) {
input = new StringTokenizer(data, ";");
String title = input.nextToken();
String author = input.nextToken();
String publisher = input.nextToken();
String genre = input.nextToken();
int year = Integer.parseInt(input.nextToken());
int page = Integer.parseInt(input.nextToken());
B = new Book(title, author, publisher, genre, year, page);
bookList.add(B);
int count = 0;
for (int i = 0; i < bookList.size(); i++) {
B = (Book) bookList.get(i);
System.out.println(B.toString());
System.out.println("=============================");
count++;
}
System.out.println("Number of Books: " + count);
}
}
Here is the screenshot for successful execution of the code.

Get and store variables from a text file in Java using scanner

i've been trying to search how do I separate the variable name and content in the txt file and store each in the variable of the object but I can't seem to find one. The code below works only if I don't add the variable names so I'm wondering how can I split it so that I can assign the content of the file to the object.
subjects.txt
id=1 name=biology instructor=John Smith room=2
Java file
public class Subject {
Integer id;
String name;
String instructor;
Integer room;
public void importSubject() throws IOException{
//gets data from file and places it into variable
File list = new File("subjects.txt");
Scanner reader = new Scanner(list);
while (reader.hasNextLine()) {
String [] data = reader.newLine.split("=");
this.id = Integer.parseInt(data[0]);
this.name = data[1];
this.instructor = data[2];
this.room = Integer.parseInt(data[3]);
}
}
}
Assuming the contents of the file always go in the order of "id", "name", "instructor", "room", one way to do this is to use a custom delimiter for the scanner:
Scanner reader = new Scanner(list);
reader.useDelimiter("\\s*(id|name|instructor|room)=");
id=, name=, instructor= and room= (including leading spaces) all match the delimiter pattern \s*(id|name|instructor|room)=, so the scanner will only give us the tokens in between those delimiters, which are:
1
biology
John Smith
2
exactly the things you want.
So you would do:
if (reader.hasNextLine()) {
this.id = reader.nextInt();
this.name = reader.next();
this.instructor = reader.next();
this.room = reader.nextInt();
}
I'm not sure why you are using a loop - you only have one set of fields to initialise. If there are multiple lines in the file and you want to create a Subject for each line, you'd create a ArrayList<Subject>:
public static List<Subject> readSubjects() throws IOException {
File list = new File("subjects.txt");
Scanner reader = new Scanner(list);
ArrayList<Subject> list = new ArrayList<>();
while (reader.hasNextLine()) {
Subject newSubject = new Subject();
newSubject.id = reader.nextInt();
newSubject.name = reader.next();
newSubject.instructor = reader.next();
newSubject.room = reader.nextInt();
list.add(newSubject);
}
return list;
}
The idea is that after you after split by = you get a part. And you have to split that part by " ", the last part is the key of the next = part, and before that is the value of the previous = split part.
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Scanner;
public class Main2 {
public static void main(String[] args) throws Exception {
Subject subject = new Subject();
subject.importSubject();
System.out.println(subject);
}
}
class Subject {
Integer id;
String name;
String instructor;
Integer room;
public void importSubject() throws IOException {
//gets data from file and places it into variable
File list = new File("subjects.txt");
Scanner reader = new Scanner(list);
while (reader.hasNextLine()) {
// biology instructor --- John Smith room
String[] stringBetweenEqualsSign = reader.nextLine().split("=");
String[] afterId = stringBetweenEqualsSign[1].split(" ");
this.id = Integer.parseInt(String.join(" ", Arrays.copyOf(afterId, afterId.length - 1)));
String[] afterName = stringBetweenEqualsSign[2].split(" ");
this.name = String.join(" ", Arrays.copyOf(afterName, afterName.length - 1));
String[] afterInstructor = stringBetweenEqualsSign[3].split(" ");
this.instructor = String.join(" ", Arrays.copyOf(afterInstructor, afterInstructor.length - 1));
this.room = Integer.parseInt(stringBetweenEqualsSign[4]);
}
}
#Override
public String toString() {
return "Subject{" +
"id=" + id +
", name='" + name + '\'' +
", instructor='" + instructor + '\'' +
", room=" + room +
'}';
}
}

optimising the search time in hashmap

I have a csv file which is hashmapped, whenever the user enter the city name(key) it will display all the details of that city. I have to optimize the search result time, everytime the it is reading the file(instead of only once) and displaying the values.
The CSV files contains data like this :
city,city_ascii,lat,lng,country,iso2,iso3,admin_name,capital,population,id
Malishevë,Malisheve,42.4822,20.7458,Kosovo,XK,XKS,Malishevë,admin,,1901597212
Prizren,Prizren,42.2139,20.7397,Kosovo,XK,XKS,Prizren,admin,,1901360309
Zubin Potok,Zubin Potok,42.9144,20.6897,Kosovo,XK,XKS,Zubin
Potok,admin,,1901608808
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
import java.io.IOException;
public class CSVFileReaders{
public static void main(String[] args) {
String filePath = "C:\\worldcities1.csv";
Scanner in = new Scanner(System.in);
System.out.println(" \n Enter the City name to be Searched : \n _> ");
long start = System.currentTimeMillis();
String searchTerm = in.nextLine();
readAndFindRecordFromCSV(filePath, searchTerm);
long end = System.currentTimeMillis();
System.out.println(" \n It took " + (end - start) + " Milli Seconds to search the result \n");
in.close();
}
public static void readAndFindRecordFromCSV( String filePath, String searchTerm) {
try{
HashMap<String,ArrayList<String>> cityMap = new HashMap<String,ArrayList<String>>();
Scanner x = new Scanner (new File(filePath),"UTF-8");
String city= "";
while(x.hasNextLine()) {
ArrayList<String> values = new ArrayList<String>();
String name = x.nextLine();
//break each line of the csv file to its elements
String[] line = name.split(",");
city = line[1];
for(int i=0;i<line.length;i++){
values.add(line[i]);
}
cityMap.put(city,values);
}
x.close();
//Search the city
if(cityMap.containsKey(searchTerm)) {
System.out.println("City name is : "+searchTerm+"\nCity details are accordingly in the order :"
+ "\n[city , city_ascii , lat , lng , country , iso2 , iso3 , admin_name , capital , population , id] \n"
+cityMap.get(searchTerm)+"");
}
else {
System.out.println("Enter the correct City name");
}
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}`
the time should be optimized and every time i search it is reading the entire file(which should happen)
Currently you mix the map initialization inside the search function.
You don't want that.
First, init the map, then use it in the search function.
To do that, extract a method for statements that instantiate and value the map and then refactor the readAndFindRecordFromCSV() method so that it accepts a Map as additional parameter :
public static void readAndFindRecordFromCSV( String filePath, String searchTerm, HashMap<String,ArrayList<String>> dataByCity) {...}
With refactoring IDE features, it should be simple enough : "extracting method" then "change signature".
Here is a code (not tested at runtime but tested at compile time) that splits the logical in separated tasks and also rely on instance methods :
public class CSVFileReaders {
private final String csvFile;
private HashMap<String, ArrayList<String>> cityMap;
private final Scanner in = new Scanner(System.in);
public static void main(String[] args) {
String filePath = "C:\\worldcities1.csv";
CSVFileReaders csvFileReaders = new CSVFileReaders(filePath);
csvFileReaders.createCitiesMap();
csvFileReaders.processUserFindRequest(); // First search
csvFileReaders.processUserFindRequest(); // Second search
}
public CSVFileReaders(String csvFile) {
this.csvFile = csvFile;
}
public void createCitiesMap() {
cityMap = new HashMap<>();
try (Scanner x = new Scanner(new File(csvFile), "UTF-8")) {
String city = "";
while (x.hasNextLine()) {
ArrayList<String> values = new ArrayList<String>();
String name = x.nextLine();
//break each line of the csv file to its elements
String[] line = name.split(",");
city = line[1];
for (int i = 0; i < line.length; i++) {
values.add(line[i]);
}
cityMap.put(city, values);
}
x.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
public void processUserFindRequest() {
System.out.println(" \n Enter the City name to be Searched : \n _> ");
long start = System.currentTimeMillis();
String searchTerm = in.nextLine();
long end = System.currentTimeMillis();
System.out.println(" \n It took " + (end - start) + " Milli Seconds to search the result \n");
//Search the city
if (cityMap.containsKey(searchTerm)) {
System.out.println("City name is : " + searchTerm + "\nCity details are accordingly in the order :"
+ "\n[city , city_ascii , lat , lng , country , iso2 , iso3 , admin_name , capital , population , id] \n"
+ cityMap.get(searchTerm) + "");
} else {
System.out.println("Enter the correct City name");
}
}
}
The interesting part is here :
String filePath = "C:\\worldcities1.csv";
CSVFileReaders csvFileReaders = new CSVFileReaders(filePath);
csvFileReaders.createCitiesMap();
csvFileReaders.processUserFindRequest(); // First search
csvFileReaders.processUserFindRequest(); // Second search
The logical is clearer now.
Why do you create / load the CSV into a HashMap with every search ?
Just create the HashMap only once in the beginning, and then on every search just check whether it exists in the HashMap, eg move the read part into a separate method :
HashMap<String,ArrayList<String>> cityMap = new HashMap<String,ArrayList<String>>();
public static void readCSVIntoHashMap( String filePath) {
try{
Scanner x = new Scanner (new File(filePath),"UTF-8");
String city= "";
while(x.hasNextLine()) {
ArrayList<String> values = new ArrayList<String>();
String name = x.nextLine();
//break each line of the csv file to its elements
String[] line = name.split(",");
city = line[1];
for(int i=0;i<line.length;i++){
values.add(line[i]);
}
cityMap.put(city,values);
}
x.close();
...
}
Then have a separate method for searching :
public static void search(String searchTerm) {
if(cityMap.containsKey(searchTerm)) {
...
}
}

How to stop getting null on my arrayList

For this code I am supposed to read in a file and make it into an ArrayList. I keep getting half of my ArrayList, and the other half comes up as what I declared the variable to be.
The code is supposed to be running through another class, which is extended.
public static void inputDoctorRecords(ArrayList<Person> doc) throws FileNotFoundException {
//opening file
Scanner console = new Scanner(new File("Doctor.dat"));
Doctor dr;
String lastName = null;
String firstName = null;
String Street = null;
String City = null;
String State = null;
String Zip = null;
int Ident = 0;
String loc = null;
double sal = 0.0;
String Spec = null;
boolean done = true;
boolean fin = true;
String sys = null;
while(console.hasNext() ){
String names = console.next();
dr = new Doctor(lastName, firstName, Street,State, City, Zip, Ident, loc, sal, Spec);
if (names.equals("LName")){
lastName = console.next();
}
if(names.equals("FName")){
firstName = console.next();
}
if (names.equals("Street")){
Street = console.nextLine();
}
if (names.equals("City")){
City = console.nextLine();
}
if (names.equals("State")){
State = console.next();
}
if (names.equals("Zip")){
Zip = console.next();
}
if (names.equals("Ident")){
Ident = console.nextInt();
}
if (names.equals("Loc")){ //returns null
loc = console.next();
}
if (names.equals("Salary")){ // returns 0.0
sal = console.nextDouble();
}
if (names.equals("Spec")){ // returns null
Spec = console.next();
}
if (names.equals("next")){ // goes to the next person in file
doc.remove(dr);
doc.add(dr);
}
}
dr = new Doctor(lastName, firstName, Street,State, City, Zip, Ident, loc, sal, Spec);
doc.remove(dr);
doc.add( dr);
System.out.println( doc);
}
// here is the input file
LName Builder
FName Robert
DOByy 1985
DOBmm 12
DOBdd 31
Ident 123456
Loc Seattle
Spec Cardiology
Salary 100000.0
Street 123 Mockingjay
City Sector 12
State WA
Zip 98058
next
LName Builder
FName Roberta
DOByy 1988
DOBmm 11
DOBdd 22
Ident 234567
Loc Fife
Spec Oncology
Salary 120000.0
Street 123 Mockingjay
City Sector 12
State WA
Zip 98058
next
LName Klein
DOByy 1974
DOBdd 06
Loc Tacoma
Street 59 West Rodeo Drive
Ident 345678
City Hollywood
DOBmm 05
State CA
FName Calvin
Zip 90210
Spec Dermatology
Salary 150000.0
eof
Try with this
public static void inputDoctorRecords(ArrayList<Person> doc) throws FileNotFoundException {
//opening file
Scanner console = new Scanner(new File("Doctor.dat"));
Doctor dr;
String lastName = null;
String firstName = null;
String Street = null;
String City = null;
String State = null;
String Zip = null;
int Ident = 0;
String loc = null;
double sal = 0.0;
String Spec = null;
boolean done = true;
boolean fin = true;
String sys = null;
while(console.hasNext() ) {
String names = console.next();
if (names.equals("LName")) {
lastName = console.next();
}
else if(names.equals("FName")) {
firstName = console.next();
}
else if (names.equals("Street")) {
Street = console.nextLine();
}
else if (names.equals("City")) {
City = console.nextLine();
}
else if (names.equals("State")) {
State = console.next();
}
else if (names.equals("Zip")) {
Zip = console.next();
}
else if (names.equals("Ident")) {
Ident = console.nextInt();
}
else if (names.equals("Loc")) {
loc = console.next();
}
else if (names.equals("Salary")) {
sal = console.nextDouble();
}
else if (names.equals("Spec")) {
Spec = console.next();
}
else if (names.equals("next")) { // goes to the next person in file
dr = new Doctor(lastName, firstName, Street,State, City, Zip, Ident, loc, sal, Spec);
doc.add(dr);
}
else {
console.nextLine(); // ignore the line because it is unknown
}
}
// Last Line
dr = new Doctor(lastName, firstName, Street,State, City, Zip, Ident, loc, sal, Spec);
doc.add(dr);
System.out.println(doc);
}
Hope this helps
The Scanner reads from InputStreams line by line, when you say for example Zip = console.next();, it discards everything it got from the current line. I recommend splitting the String using String#split().
EDIT: Scanner#next() consumes the string. Thats the word I was looking for.

Categories

Resources