2015 FRQ by Paaras Purohit - Question 4
Completing the 2015 AP Computer Science A free-response questions and taking notes - Question 4
Question 4
Type: Writing classes
This question involves the design of an interface, writing a class that implements the interface, and writing a method that uses the interface.
(a) A number group represents a group of integers defined in some way. It could be empty, or it could contain one or more integers.
Write an interface named NumberGroup that represents a group of integers. The interface should have a single contains method that determines if a given integer is in the group. For example, if group1 is of type NumberGroup, and it contains only the two numbers -5 and 3, then group1.contains(-5) would return true, and group1.contains(2) would return false. Write the complete NumberGroup interface. It must have exactly one method.
(b) A range represents a number group that contains all (and only) the integers between a minimum value and a maximum value, inclusive. Write the Range class, which is a NumberGroup. The Range class represents the group of int values that range from a given minimum value up through a given maximum value, inclusive. For example,the declaration
NumberGroup range1 = new Range(-3, 2);
represents the group of integer values -3, -2, -1, 0, 1, 2.
Write the complete Range class. Include all necessary instance variables and methods as well as a constructor that takes two int parameters. The first parameter represents the minimum value, and the second parameter represents the maximum value of the range. You may assume that the minimum is less than or equal to the maximum.
(c) The MultipleGroups class (not shown) represents a collection of NumberGroup objects and isa NumberGroup. The MultipleGroups class stores the number groups in the instance variable groupList (shown below), which is initialized in the constructor.
private List
Write the MultipleGroups method contains. The method takes an integer and returns true if and only if the integer is contained in one or more of the number groups in groupList.
For example, suppose multiple1 has been declared as an instance of MultipleGroups and consists of the three ranges created by the calls new Range(5, 8), new Range(10, 12), and new Range(1, 6). The following table shows the results of several calls to contains.
Skills I’m Familiar With:
- Writing classes
- Comparing types of variables
- Encapsulation and method/class security
Skills I’m Unfamiliar With
- Writing interfaces
- The general algorithms involved in this, as they revolve around writing interfaces
My Solution (With Research):
Technically, I had to do research on this. While I do have some knowledge of interfaces, what they are, and how to use them, I am not familiar with them to the point where I can use them fluently with the rest of Java.
public interface NumberGroup {
boolean contains(int number);
}
public class Range implements NumberGroup {
private int min;
private int max;
public Range(int min, int max) {
this.min = min;
this.max = max;
}
@Override
public boolean contains(int number) {
return (number >= min && number <= max);
}
}
public class MultipleGroups implements NumberGroup {
private List<NumberGroup> groupList;
public MultipleGroups(List<NumberGroup> groupList) {
this.groupList = groupList;
}
@Override
public boolean contains(int number) {
for (NumberGroup group : groupList) {
if (group.contains(number)) {
return true;
}
}
return false;
}
}
public class Main {
public static void main(String[] args) {
NumberGroup range1 = new Range(-3, 2);
NumberGroup range2 = new Range(5, 8);
NumberGroup range3 = new Range(10, 12);
NumberGroup range4 = new Range(1, 6);
List<NumberGroup> groupList = new ArrayList<>();
groupList.add(range2);
groupList.add(range3);
groupList.add(range4);
MultipleGroups multiple1 = new MultipleGroups(groupList);
System.out.println(multiple1.contains(3));
System.out.println(multiple1.contains(9));
}
}
Main.main(null);
true
false
From APCSA FRQs to Our Project
How the methods and control structure algorithms relates to our in-class project
Although we don’t use interfaces in our project, we do use the use of classes, and we use the concepts from APCSA to dictate how the child classes and parent classes, depending on who they inherit, should interact with each other. An example of this can be found in our JWT algorithms. Below is an example of how the JWT controller, authentication, authentication filter, request filter, and token utility classes interact, and how JwtAuthenticationEntryPoint.java
has the same interface algorithms as the FRQ.
JwtAuthenticationEntryPoint.java:
package com.nighthawk.spring_portfolio.mvc.jwt;
import java.io.IOException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
As you can see, this classes uses interface algorithms when it overrides the commence()
method.
JwtApiController.java:
package com.nighthawk.spring_portfolio.mvc.jwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import com.nighthawk.spring_portfolio.mvc.person.Person;
import com.nighthawk.spring_portfolio.mvc.person.PersonDetailsService;
@RestController
@CrossOrigin(origins = "*")
public class JwtApiController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private PersonDetailsService personDetailsService;
@PostMapping("/authenticate")
public ResponseEntity<?> createAuthenticationToken(@RequestBody Person authenticationRequest) throws Exception {
System.out.println(authenticationRequest.getEmail());
System.out.println(authenticationRequest.getPassword());
authenticate(authenticationRequest.getEmail(), authenticationRequest.getPassword());
final UserDetails userDetails = personDetailsService
.loadUserByUsername(authenticationRequest.getEmail());
final String token = jwtTokenUtil.generateToken(userDetails);
final ResponseCookie tokenCookie = ResponseCookie.from("jwt", token)
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(3600)
.sameSite("None; Secure")
// .domain("example.com") // Set to backend domain
.build();
System.out.println("returning!");
return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, tokenCookie.toString()).build();
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
} catch (Exception e) {
throw new Exception(e);
}
}
}
JwtAuthenticationFilter.java:
package com.nighthawk.spring_portfolio.mvc.jwt;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
// Implement the logic for JWT authentication here
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Extract and validate JWT token
// Set Authentication in the context
// Continue with the filter chain
filterChain.doFilter(request, response);
}
}
JwtRequestFilter.java:
package com.nighthawk.spring_portfolio.mvc.jwt;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
// import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.nighthawk.spring_portfolio.mvc.person.PersonDetailsService;
import io.jsonwebtoken.ExpiredJwtException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private PersonDetailsService personDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
final Cookie[] cookies = request.getCookies();
String username = null;
String jwtToken = null;
// Try to get cookie with name jwt
if ((cookies == null) || (cookies.length == 0)) {
logger.warn("No cookies");
} else {
for (Cookie cookie: cookies) {
if (cookie.getName().equals("jwt")) {
jwtToken = cookie.getValue();
}
}
if (jwtToken == null) {
logger.warn("No jwt cookie");
} else {
try {
// Get username from the token if jwt cookie exists
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
} catch (Exception e) {
System.out.println("An error occurred");
}
}
}
// If no cookies have name jwt return warning
// Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.personDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set
// authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the
// Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
JwtTokenUtil.java:
package com.nighthawk.spring_portfolio.mvc.jwt;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Base64;
import java.util.function.Function;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.*;
@Component
public class JwtTokenUtil {
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
@Value("${jwt.secret}")
private String secret;
private SecretKey getSecretKey() {
byte[] ptsecret = Base64.getDecoder().decode(this.secret);
SecretKey k = Keys.hmacShaKeyFor(ptsecret);
return k;
}
//retrieve username from jwt token
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
//retrieve expiration date from jwt token
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
//for retrieving any information from token we will need the secret key
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().verifyWith(getSecretKey()).build().parseSignedClaims(token).getPayload();
}
//check if the token has expired
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
//generate token for user
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
//while creating the token -
//1. Define claims of the token, like Issuer, Expiration, Subject, and the ID
//2. Sign the JWT using the HS512 algorithm and secret key.
//3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
// compaction of the JWT to a URL-safe string
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().claims(claims).subject(subject).issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
.signWith(getSecretKey()).compact();
}
//validate token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
As you can see, the JWT classes within the backend use the same conceptual algorithmic programming as seen in APCSA FRQ’s such as Question 4.